0

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

我的任务:解析一条消息并获取一个 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() {}
}
4

1 回答 1

1

正如所承诺的,一个例子: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>
于 2018-03-29T15:00:54.777 回答