Angular 5 + Angular Material Loding a Data Table

时间:2018-03-28 16:43:53

标签: angular angular-material angular5 angular-material-5

我是Angular 5 + Angular Material的新手。我正在阅读文档并试图掌握加载表格的最佳方法。我发现您可以创建一个DataSource类并使用connect方法连接到一个observable并加载该表。

我的任务:解析消息并获取一个id数组,然后调用后端获取每个id的相应对象。在数据表中显示对象列表。

我当前的解决方案:在我的服务中,我将对象传递给getAllPatients(objet),然后我得到对象Id的列表,然后循环遍历数组并为每个对象调用getPatient(patient)。然后我订阅了getPatient的结果,然后我将结果推入列表并对列表进行排序,然后使用Subject.next推出一个事件,其中包含患者列表,这是我的patientService中的全局变量。在我的数据表中,DataSource类我在connect方法中传递了主题。

我的问题:我不确定是否有任何真正的取消订阅,而且我也不确定这是否是最干净的方法......因为当我离开页面时,通话仍将继续...我最关心的是如果你进入页面然后离开并快速返回它会导致两批调用继续,然后每个对象有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)}`)
    }

  }

表的数据源类:

export class PatientDataSource extends DataSource<any> {
  constructor(private patientService: PatientService) {
    super();
  }
  connect(): Subject<any[]> {
    return this.patientService.patientSubject;
  }
  disconnect() {}
}

1 个答案:

答案 0 :(得分:1)

正如所承诺的,一个例子:https://stackblitz.com/edit/angular-enbzms?file=app%2Fsome.service.ts

那里发生了什么:在服务中,有一个方法返回进行这些HTTP调用所需的详细信息对象的BehaviorSubject。通过SwitchMap进行管道传输,在其中将所有成员对象传播并映射到单独的HTTP.get调用中(在此处使用计时器进行模拟)。 Zip将等待所有HTTP observable完成,然后返回结果数组,始终与原始数组的顺序相同。

然后,您只需要在DataSource的connect -method中返回service.getObservableForDataSource()。 MatTable将订阅创建和取消订阅销毁。

如果您在stackblitz处查看控制台,可以看到如果您点击emit details并在点击hide table后完成(完全模拟离开页面),控制台记录在那里停止,因为当MatTable取消订阅时,整个Observable连锁店“死亡”。

在这种情况下,一个简单的async管道模拟MatTable,但它的工作方式相同。

要遵守SO规则,我也会在这里复制Stackblitz链接后面的代码,但我建议只关注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>