angular - Vad är det rätta sättet att vänta på att flera förfrågningar ska slutföras?


Jag har en komponent som kräver flera GET-förfrågningar för att returnera all data jag behöver. Dessa är oberoende av varandra men jag skulle vilja göra alla anrop tillsammans och ladda användargränssnittet efter att detta har slutförts framgångsrikt.

För att uppnå detta använder jag forkJoin men jag är ny på RxJS och jag är inte helt säker på att jag gör detta korrekt. Se vänligen mitt exempel nedan. All hjälp skulle uppskattas mycket.

import { Component, Input, OnInit, ElementRef, ViewChild } from '@angular/core';
import { Observable, forkJoin, of } from 'rxjs';
import { flatMap } from 'rxjs/operators';
import { DataService, ItemGroupDto} from 'src/app/shared/services/data-service.service';
import { UserService} from 'src/app/shared/services/user-service.service';

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

export class SampleComponent implements OnInit {

  @Input() itemGroupId: number;
  public datasetOne: Observable<any[]>;
  public datasetTwo: Observable<any[]>;
  public currentApprover: string;

  constructor(
    private _dateService: DateService,
    private _userService: UserService,
  ) {}

  ngOnInit() {
    this.getData();
  }

  private _getApprover(itemGroupId: number) : Observable<ItemGroupDto> {
    const approver = this._userService.getApprover(itemGroupId);
    approver.subscribe(results => {
      const approver = results.approvers.find(approver => (
        approver.Id === 1
      ));
      this.currentApprover = approver.UserFullName;    
    })
    return approver;
  }

  private _sampleDatasetOne() : Observable<any>{
    const sampleDatasetOne = this._dataService.sampleDatasetOne();
    sampleDatasetOne.subscribe(results => {
      this.datasetOne = results;
    })
    return sampleDatasetOne;
  }

  private _sampleDatasetTwo() : Observable<any>{
    const sampleDatasetTwo = this._dataService.sampleDatasetTwo();
    sampleDatasetTwo.subscribe(results => {
      this.datasetTwo = results;
    })
    return sampleDatasetTwo;
  }


  getData() {
    let sampleDatasetOne = this._sampleDatasetTwo();
    let sampleDatasetTwo = this._sampleDatasetTwo();
    let approver = this._getApprover(this.itemGroupId);
    
    forkJoin(sampleDatasetOne, sampleDatasetTwo, approver).subscribe(_ => {
      // all observables have been completed
      this.loaded = true;
    });
  }
}

Ovanstående kod är baserad på ett exempel jag hittade när jag undersökte användningen av forkJoin för detta ändamål. Detta fungerar utmärkt för mina krav. Det gör att jag kan manipulera resultaten för varje inom deras respektive prenumerationer och sedan, när alla observables är klara, kan jag till exempel visa datan i vyn via *ngIf eller motsvarande.

Men jag har också sett exempel som följande:

let sampleDatasetOne = this._dataService.sampleDatasetOne();
let sampleDatasetTwo = this._dataService.sampleDatasetTwo();
let approver = this._userService.getApprover(this.itemGroupId);

forkJoin([sampleDatasetOne, sampleDatasetTwo, approver]).subscribe(results => {
      this.datasetOne = results[0];
      this.datasetTwo = results[1];
      const approver = results[2].approvers.find(approver => (
        approver.Id === 1
      ));
      this.currentApprover = approver.UserFullName; 
    });

Jag ser faktiskt detta ganska ofta i exempel eller handledningar online som förklarar just detta användningsområde av forkJoin. Detta verkar ganska galet, svårt att läsa, underhålla osv.. Men kanske saknar jag bara någon viktig information eller koncept?

I huvudsak letar jag bara efter rätt sätt i Angular/RxJS att vänta på att flera anrop ska slutföras och att hantera/manipulera resultaten för varje.

Om jag är på rätt spår, finns det något jag missar, gör fel eller skulle kunna förbättra?

Tack till alla som tar sig tid att svara och hjälpa mig att förstå detta.

Svar #1

Jag skulle säga att det beror på vad du gör.

Om du bara vill att alla Observables ska slutföras och sedan göra något med deras emitterade värden, hjälper lite syntaktiskt socker:

forkJoin([observable1, observable2, observable3])
  // Array destructuring, a cool EcmaScript 6 feature.
  .subscribe(([value1, value2, value3]) => {
     this.value1 = value1;
     this.value2 = value2;
     this.value3 = value3
  }
);

Detta lämnar fortfarande möjligheten öppen att bearbeta observables innan de skickas till forkJoin, t.ex.:

const mappedObservable1 = observable1
  .pipe(
     map(value1 => mapToSomething(value1))
     tap(mappedValue1 => this.value1 = mappedValue1)
  );

forkJoin([mappedObservable1, observable2, observable3]).subscribe(() =>
  this.loadingDone = true;
)

Svar #2

Koden du tillhandahöll verkar bra. För underhållbarhet och läsbarhet är det vanligtvis bäst att följa det avslutande $när du namnger observables. Istället för att prenumerera och sätta värden kan du helt enkelt upprätthålla en observable som har värdena mappade i det objektformat du vill binda till mallen. Detta kommer att göra det mindre kod och renare.

I det här fallet, något sådant här:

let sampleDatasetOne$ = this._dataService.sampleDatasetOne();
let sampleDatasetTwo$ = this._dataService.sampleDatasetTwo();
let approver$ = this._userService.getApprover(this.itemGroupId);

let data$ = forkJoin([sampleDatasetOne$, sampleDatasetTwo$, approver$]).pipe(map(results => ({
      dataSetOne: results[0],
      dataSetTwo: results[1],
      approver: results[2].approvers.find(approver => (
        approver.Id === 1
      ))})));

data$ kan asynkront pipas in mallen (vilket kommer att prenumerera och avprenumerera). Dessutom kan din mappade funktion starkt typa returvärdet så att det kan kontrolleras i mallen eller någon annanstans i komponenten som ett Observable. Hoppas detta var till hjälp.

Men forkJoin verkar vara den rätta operatorn att använda i det här fallet :)