Angular 5+需要组件来更新数据更改

时间:2018-04-22 17:32:26

标签: angular rxjs observable

我使用observable从服务中填充可用的tile组件,该服务从远程json文件获取其内容(使用json-server和localhost)。这部分有效。

tile.service.ts:

 @Injectable()
export class TileService {

  tiles$ = new Subject<any>();
  details$  = new Subject<any>();
  messages$  = new Subject<any>();

  private tilesUrl = 'http://localhost:3000/tiles';

  private httpOptions = {
    headers: new HttpHeaders({ 'Content-Type': 'application/json' })
  };

  constructor(private http: HttpClient) { }

  getTiles(): Observable<Tile[]> {
    return this.http.get<Tile[]>(this.tilesUrl);
  }

  tileStream(data) {
    this.tiles$.next(data);
  }

  detailStream(data) {
   this.details$.next(data);
  }

  messageStream(data) {
    this.messages$.next(data);
   }

   createTile(t: Tile) {
    return this.http.post<Tile>(this.tilesUrl, t, this.httpOptions).subscribe(
      res => {
        console.log(res);
      },
      (err: HttpErrorResponse) => {
        console.log(err.error);
        console.log(err.name);
        console.log(err.message);
        console.log(err.status);
      }
    );
   }

}

但是,当我添加新磁贴时,可用的磁贴组件不会动态更新。

可供tiles.component.ts

@Component({
  selector: 'app-available-tiles',
  templateUrl: './available-tiles.component.html',
  styleUrls: ['./available-tiles.component.css']
})

export class AvailableTilesComponent implements OnInit {

  title = 'Available Tiles';

  tiles: Tile[];

  mockNewTile = {
    id: uuid(),
    title: 'GO',
    description: 'Go is an open source programming language that makes it easy to build simple, reliable, and efficient software.',
    code: {
        snippetA: 'something here.',
        }
  };

  constructor(private tileService: TileService) { }

  ngOnInit() { // Populates available-tiles on load.  Works.
    this.tileService.getTiles().subscribe(x => this.tiles = x);
  }

  addTile(t: Tile) { // For adding tile to dashboard.
    this.tileService.tileStream(t);
    this.tileService.messageStream( t.title +  ' tile added.');
  }

  createTile() {
    this.tileService.createTile(this.mockNewTile);
  }
} 

可供tiles.component.html

<aside>
  <h3>{{ title }}</h3>
  <button *ngFor="let tile of tiles" (click) = "addTile(tile)">{{ tile.title }}</button>  
  <button (click) = "createTile()">Create Tile</button>  
</aside>

触发addTile()的第一次点击事件是填充仪表板。仪表板实际上确实动态更新。

dashboard.component.ts

@Component({
  selector: 'app-dashboard',
  templateUrl: './dashboard.component.html',
  styleUrls: ['./dashboard.component.css']
})
export class DashboardComponent implements OnInit {

  title = 'Dashboard';

  // Tiles to show on dashboard
  tiles = [];

  constructor(private tileService: TileService) { }

  ngOnInit() {
    this.tileService.tiles$.subscribe(x => this.populateTilesArr(x));
  }

  populateTilesArr(t: Tile) {
    if (this.tiles.indexOf(t) === -1) {
      this.tiles.push(t);
    }
  }

4 个答案:

答案 0 :(得分:0)

问题是您在订阅过程之后完全错过/离开了ngZone的步骤/代码,这意味着在检测到例如更改后下面的代码构造

new Vue({
    el: '#exercise',
    data: {
        yourRoll: '10',
        passCheck: '10',    
    },
    methods: {
        roll20: function(){
            this.yourRoll = Math.round(Math.random()*19)+1;
            return this.yourRoll;
        },
        newCheck: function(){
            this.passCheck = Math.round(Math.random()*19)+1;
        }
    }
});

您可以在此链接中详细了解使用ngZones所做的更改:https://blog.thoughtram.io/angular/2017/02/21/using-zones-in-angular-for-better-performance.html

答案 1 :(得分:0)

http.get()一旦完成就会开火,所以

this.tileService.getTiles().subscribe(x => this.tiles = x)

也只会点燃一次。

也许这样做可以解决眼前的需求,

createTile() {
  this.tileService.createTile(this.mockNewTile).subscribe(x => {
    this.tileService.getTiles().subscribe(x => this.tiles = x);
  });
}

并更改服务,以便您返回POST完成而不是订阅,

createTile(t: Tile) {
 return this.http.post<Tile>(this.tilesUrl, t, this.httpOptions)
   .map(res => console.log(res))
   .catch((err: HttpErrorResponse) => {
     ...
   });

更好的方法

但是,您可能会从其他位置发布新的图块,因此请订阅您的服务Subject而不是

ngOnInit() {
  this.tileService.tiles$.subscribe(x => this.tiles = x);
  this.tileService.getTiles()
}

,服务需要

@Injectable()
export class TileService {

  tiles$ = new Subject<Tile[]>();

  private tilesUrl = 'http://localhost:3000/tiles';

  private httpOptions = {
    headers: new HttpHeaders({ 'Content-Type': 'application/json' })
  };

  constructor(private http: HttpClient) { }

  getTiles(): void {
    this.http.get<Tile[]>(this.tilesUrl)
      .subscribe(tiles => this.tileStream(tiles));
  }

  tileStream(data) {
    this.tiles$.next(data);
  }

  createTile(t: Tile) {
    this.http.post<Tile>(this.tilesUrl, t, this.httpOptions)
      .catch((err: HttpErrorResponse) => {
        ...
      })
      .subscribe(res => this.getTiles())
  }

}

答案 2 :(得分:0)

在直接更新变量时,您通常必须使用NgZone才能在模板中更新。如果你想得到想象,可以在Observable序列上试用异步管道,例如使用ReplaySubject,它存储输入的所有值,而不是tile的数组。

<强> 1。使用NgZone运算符

dashboard.component.ts

import { NgZone } from '@angular/core';
...
constructor(private tileService: TileService, private zone: NgZone) { }
...
ngOnInit() {
    this.tileService.tiles$.subscribe(x => this.zone.run(() => this.populateTilesArr(x)));
}

<强> 2。使用带有ReplaySubject的异步管道

可供tiles.component.html

<aside>
  <h3>{{ title }}</h3>
  <button *ngFor="let tile of tiles | async" (click) = "addTile(tile)">{{ tile.title }}</button>  
  <button (click) = "createTile()">Create Tile</button>  
</aside>

dashboard.component.ts

...
tiles = (new ReplaySubject<any>()).distinct(); //Stores all values added, but only emits distinct ones

  constructor(private tileService: TileService) { }

  ngOnInit() {
    this.tileService.tiles$.subscribe(x => this.populateTilesArr(x));
  }

  populateTilesArr(t: Tile) {
    this.tiles.next(t);
  }

答案 3 :(得分:0)

感谢所有回复的人。特别感谢Richard Matsen,他抽出时间帮助我解决这个问题。

tile.service.ts:

@Injectable()
export class TileService {

  availableTiles$ = new Subject<Tile[]>();
  tilesForDashboard$ = new Subject<Tile>();
  details$  = new Subject<Tile>();
  messages$  = new Subject<any>();

  private tilesUrl = 'http://localhost:3000/tiles';

  private httpOptions = {
    headers: new HttpHeaders({ 'Content-Type': 'application/json' })
  };

  constructor(private http: HttpClient) { }

  getTiles(): void {
     this.http.get<Tile[]>(this.tilesUrl)
    .subscribe(tiles => this.availableTilesStream(tiles));
  }

  availableTilesStream(data) {
    this.availableTiles$.next(data);
  }

  dashboardStream(data) {
    this.tilesForDashboard$.next(data);
  }

  detailStream(data) {
   this.details$.next(data);
  }

  messageStream(data) {
    this.messages$.next(data);
   }

  createTile(t: Tile) {
    return this.http.post<Tile>(this.tilesUrl, t, this.httpOptions)
      .catch((err: HttpErrorResponse) => {
        console.log(err.error);
        console.log(err.name);
        console.log(err.message);
        console.log(err.status);
        return Observable.empty();
      }).subscribe(res => this.getTiles());
  }

}

可供tiles.ts:

@Component({
  selector: 'app-available-tiles',
  templateUrl: './available-tiles.component.html',
  styleUrls: ['./available-tiles.component.css']
})

export class AvailableTilesComponent implements OnInit {

  title = 'Available Tiles';

  tiles: Tile[];

  mockNewTile = {
    id: uuid(),
    title: 'GO',
    description: 'Go is an open source programming language that...',
    code: {
        snippetA: 'something here.',
        }
  };

  constructor(private tileService: TileService) { }

  ngOnInit() {
    this.tileService.availableTiles$.subscribe(x => this.tiles = x);
    this.tileService.getTiles();
  }

  addTileToDashboard(t: Tile) {
    this.tileService.dashboardStream(t);
    this.tileService.messageStream( t.title +  ' tile added.');
  }

  createTile() {
    this.tileService.createTile(this.mockNewTile);
    }

  }

可供tiles.component.html:

<aside>
  <h3>{{ title }}</h3>
  <button *ngFor="let tile of tiles" (click) = "addTileToDashboard(tile)">{{ tile.title }}</button>  
  <button (click) = "createTile()">Create Tile</button>  
</aside>

dashboard.component.ts

@Component({
  selector: 'app-dashboard',
  templateUrl: './dashboard.component.html',
  styleUrls: ['./dashboard.component.css']
})
export class DashboardComponent implements OnInit {

  title = 'Dashboard';

  // Tiles to show on dashboard
  tiles = [];

  // A property to hold the selected tile in the dashboard.
  selectedTile: Tile;

  editing = false;

  constructor(private tileService: TileService) { }

  ngOnInit() {
    // We only want tiles that have been clicked on in available-tiles.component to show in the dashboarrd.
    // The available-tiles.component adds to the tilesForDashboard$ subject when a tile is clicked.
    // Populate the tiles array based on the data stream from the tilesForDashboard$ subject.
    this.tileService.tilesForDashboard$.subscribe(x => this.uniqueTiles(x));
  }

  // Only add a single occurence of each tile to the tiles array
  uniqueTiles(t: Tile) {
    if (this.tiles.indexOf(t) === -1) {
      this.tiles.push(t);
    }
  }

  remove(t) {
     // remove tile from array
    const index = this.tiles.indexOf(t);
    this.tiles.splice(index, 1);
    // If the value passed into remove(tile) is equal to the selectedTile, then
    // pass null to the details subject in the detailStream method of the service.
    // Otherwise, if no check performed (simply passing null) then any
    // detail showing gets removed when any tile is removed from the dashboard.
    if (this.selectedTile === t) {
      this.tileService.detailStream(null);
    }

    if (this.tiles.length < 1) {
      this.editing = false;
    }
    this.tileService.messageStream( t.title +  ' tile removed.');
  }

  displayDetails(t: Tile) {
    this.tileService.detailStream(t);
    this.selectedTile = t;
  }

}

dashboard.component.html:

 <ul>
      <li *ngFor = "let tile of tiles" (click)="displayDetails(tile)">
        <button  *ngIf="editing" class="remove" (click)="remove(tile)">X</button>
        <span class="title">{{tile.title}}</span> 
        <span class="desc">{{tile.description}}</span>
    </li>
  </ul>