Angular 5 + Angular Material 加载数据 Table
Angular 5 + Angular Material Loading a Data Table
我是 Angular 5 + Angular Material 的新人。我正在通读文档并试图掌握加载 table 的最佳方式。我发现您可以创建一个 DataSource class 并使用 connect 方法连接到一个 observable 并加载 table。
我的任务:解析一条消息并获取一个id数组,然后调用后端为每个id获取相应的对象。显示数据 table.
中的对象列表
我当前的解决方案:在我的服务中,我将对象传递给 getAllPatients(objet),然后获取对象 ID 的列表,然后遍历数组并为每个对象调用 getPatient(patient)。然后我订阅 getPatient 的结果,然后将结果推送到列表中并对列表进行排序,然后使用 Subject.next 推送一个包含患者列表的事件,这是我的 patientService 中的全局变量。在我的数据 tables DataSource class 中,我在连接方法中传递了主题。
我的问题:我不确定是否有任何真正的取消订阅发生,而且我不确定这是否是最干净的方法..因为当我离开页面时,呼叫仍会继续...我最大的担心的是,如果您进入页面然后离开并快速返回,是否会导致两批调用继续,然后每个对象都有 2 个?好像没有发生但是我有点担心。
代码:
我服务的功能:
getPatientDemographics(id): Observable<any> {
return this.http.get(this.userUrl + id )
}
getAllPatients(details) {
this.patients = []
var membersObj = details.getMembersObj()
if (membersObj){
for (var member of membersObj.member) {
this.getPatientDemographics(details.getMemberId(member)).subscribe(
data => {
this.patients.push(new Patient(data))
this.patients.sort(this.sortingUtil.nameCompare)
this.patientSubject.next(this.patients)
console.log(`success id ${details.getMemberId(member)}`)
},
error => {
console.log(`member id ${details.getMemberId(member)}`)
this.patientSubject.error('errr')
}
)
}
}else {
console.log(`member fail ${JSON.stringify(membersObj)}`)
}
}
table 的数据源 class:
export class PatientDataSource extends DataSource<any> {
constructor(private patientService: PatientService) {
super();
}
connect(): Subject<any[]> {
return this.patientService.patientSubject;
}
disconnect() {}
}
如约而至,举个例子:https://stackblitz.com/edit/angular-enbzms?file=app%2Fsome.service.ts
所以那里发生了什么:在服务中,有一个方法,它 return 是进行这些 HTTP 调用所需的详细信息对象的 BehaviorSubject。通过 SwitchMap 对其进行管道传输,在其中将所有成员对象展开并映射到单独的 HTTP.get 调用中(此处使用计时器进行模拟)。 Zip 将等到所有 HTTP 可观察量都完成,然后 return 您的结果数组始终与原始数组的顺序相同。
然后你只需要 return service.getObservableForDataSource() 在你的数据源的连接方法中。 MatTable 将在创建时订阅并在销毁时取消订阅。
如果您在 stackblitz 时查看控制台,您会发现如果单击 emit details
并在单击 hide table
后不久(完美模拟离开页面),当 MatTable 取消订阅时,控制台日志记录就在那里停止,因为整个 Observable 链 'dies'。
在这种情况下,有一个简单的 async
管道模拟 MatTable,但它的工作方式相同。
为了遵守 SO 规则,我也会在此处复制 Stackblitz link 背后的代码,但我建议只遵循 link 到 Stackblitz :)
some.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>
我是 Angular 5 + Angular Material 的新人。我正在通读文档并试图掌握加载 table 的最佳方式。我发现您可以创建一个 DataSource class 并使用 connect 方法连接到一个 observable 并加载 table。
我的任务:解析一条消息并获取一个id数组,然后调用后端为每个id获取相应的对象。显示数据 table.
中的对象列表我当前的解决方案:在我的服务中,我将对象传递给 getAllPatients(objet),然后获取对象 ID 的列表,然后遍历数组并为每个对象调用 getPatient(patient)。然后我订阅 getPatient 的结果,然后将结果推送到列表中并对列表进行排序,然后使用 Subject.next 推送一个包含患者列表的事件,这是我的 patientService 中的全局变量。在我的数据 tables DataSource class 中,我在连接方法中传递了主题。
我的问题:我不确定是否有任何真正的取消订阅发生,而且我不确定这是否是最干净的方法..因为当我离开页面时,呼叫仍会继续...我最大的担心的是,如果您进入页面然后离开并快速返回,是否会导致两批调用继续,然后每个对象都有 2 个?好像没有发生但是我有点担心。
代码:
我服务的功能:
getPatientDemographics(id): Observable<any> {
return this.http.get(this.userUrl + id )
}
getAllPatients(details) {
this.patients = []
var membersObj = details.getMembersObj()
if (membersObj){
for (var member of membersObj.member) {
this.getPatientDemographics(details.getMemberId(member)).subscribe(
data => {
this.patients.push(new Patient(data))
this.patients.sort(this.sortingUtil.nameCompare)
this.patientSubject.next(this.patients)
console.log(`success id ${details.getMemberId(member)}`)
},
error => {
console.log(`member id ${details.getMemberId(member)}`)
this.patientSubject.error('errr')
}
)
}
}else {
console.log(`member fail ${JSON.stringify(membersObj)}`)
}
}
table 的数据源 class:
export class PatientDataSource extends DataSource<any> {
constructor(private patientService: PatientService) {
super();
}
connect(): Subject<any[]> {
return this.patientService.patientSubject;
}
disconnect() {}
}
如约而至,举个例子:https://stackblitz.com/edit/angular-enbzms?file=app%2Fsome.service.ts
所以那里发生了什么:在服务中,有一个方法,它 return 是进行这些 HTTP 调用所需的详细信息对象的 BehaviorSubject。通过 SwitchMap 对其进行管道传输,在其中将所有成员对象展开并映射到单独的 HTTP.get 调用中(此处使用计时器进行模拟)。 Zip 将等到所有 HTTP 可观察量都完成,然后 return 您的结果数组始终与原始数组的顺序相同。
然后你只需要 return service.getObservableForDataSource() 在你的数据源的连接方法中。 MatTable 将在创建时订阅并在销毁时取消订阅。
如果您在 stackblitz 时查看控制台,您会发现如果单击 emit details
并在单击 hide table
后不久(完美模拟离开页面),当 MatTable 取消订阅时,控制台日志记录就在那里停止,因为整个 Observable 链 'dies'。
在这种情况下,有一个简单的 async
管道模拟 MatTable,但它的工作方式相同。
为了遵守 SO 规则,我也会在此处复制 Stackblitz link 背后的代码,但我建议只遵循 link 到 Stackblitz :)
some.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>