正如所承诺的,一个例子:https ://stackblitz.com/edit/angular-enbzms?file=app%2Fsome.service.ts
那么那里发生了什么:在服务中,有一个方法返回进行这些 HTTP 调用所需的详细信息对象的 BehaviorSubject。通过 SwitchMap 将其通过管道传输,您可以在其中将所有成员对象传播并映射到单独的 HTTP.get 调用(此处使用计时器模拟)。Zip 将等到所有 HTTP 可观察对象都完成后,然后始终以与原始数组相同的顺序返回结果数组。
然后你只需要在你的 DataSource 的 connect 方法中返回 service.getObservableForDataSource() 。MatTable 将在创建时订阅并在销毁时取消订阅。
如果您在stackblitz时查看控制台,您可以看到,如果您单击emit details
并在单击后不久hide table
(这完全模拟离开页面),控制台日志记录会停在那里,因为当 MatTable 取消订阅时,整个 Observable 链“死亡” .
在这种情况下,有一个简单的async
管道来模拟 MatTable,但它的工作方式相同。
为了遵守 SO 规则,我也会在此处复制 Stackblitz 链接后面的代码,但我建议您只关注 Stackblitz 的链接 :)
一些.service.ts
import { Injectable } from '@angular/core';
import { timer } from 'rxjs/observable/timer';
import { zip } from 'rxjs/observable/zip';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Observable';
import { map, switchMap, filter, tap } from 'rxjs/operators';
@Injectable()
export class SomeService {
constructor() { }
details$: BehaviorSubject<any> = new BehaviorSubject(null);
loading$: BehaviorSubject<boolean> = new BehaviorSubject(false);
getPatientDemographics(id): Observable<any> {
// Emulate an API call which takes a random amount of time
return timer(100 + Math.random() * 1500).pipe(
map((n: number) => {
return {
id,
created: new Date().getTime()
};
})
);
}
setDetails(details: any) {
this.details$.next(details);
}
getObservableForDataSource() {
// In your DataSource.connect(): return service.getObservableForDataSource()
return this.details$.pipe(
tap(() => this.loading$.next(true)),
tap(data => console.log('Details in the pipe', data)),
map(details => details.getMembersObj()),
filter(membersObj => !!membersObj), // Leave out possible nulls
map(membersObj => membersObj.member), // Pass on just the array of members
switchMap(this.getPatients.bind(this)), // Switch the observable to what getPatients returns
tap(data => console.log('End of pipe', data)),
tap(() => this.loading$.next(false)),
);
}
getPatients(members: any[]) {
return zip(
...members.map(member => this.getPatientDemographics(member.id).pipe(
tap(data => console.log('Received patient demog.', data)),
// The end result will be in the same order as the members array, thanks to 'zip'
))
);
}
}
app.component.ts
import { Component } from '@angular/core';
import { SomeService } from './some.service';
import { Observable } from 'rxjs/Observable';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
tableVisible = true;
dataSourceObservable: Observable<any>;
constructor(public service: SomeService) { }
start() {
const mockDetails = {
getMembersObj: () => {
return {
member: [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }]
}
}
};
// In your table datasource class's connect(), you would simply
// return service.getObservableForDataSource()
this.dataSourceObservable = this.service.getObservableForDataSource();
this.service.setDetails(mockDetails);
}
}
app.component.html
<h2>Fake Table</h2>
<p>
<button (click)="start()">Emit the details</button>
</p>
<p>
<button (click)="tableVisible=!tableVisible">{{tableVisible?'Hide table: emulate navigating away from this route' : 'Show table'}}</button>
</p>
<div *ngIf="tableVisible">
<div *ngIf="dataSourceObservable | async as data">
<pre>{{data|json}}</pre>
</div>
<i *ngIf="service.loading$|async">Loading...</i>
</div>