2017-12-09 4 views
1

Das Ziel besteht darin, eine Sammlung von IDs zu durchlaufen und für jede ID einen HTTP-Aufruf auszuführen. Für jede ID verwende ich einen Service mit einer Methode get(), die ein Observable zurückgibt. Jedes Mal, wenn die get() -Methode aufgerufen wird, abonniere ich das zurückkehrende Observable und versuche, das Ergebnis in ein Array zu pushen, das schließlich an eine andere Methode für eine neue Operation übergeben wird.Angular 2/4 & RxJS - Subskriptionen in forEach - Sicherstellen, dass die Flusskontrolle erst fortgesetzt wird, wenn alle Observables vollständig sind

Relevante Service-Methode:

public get(departmentId: number): Observable<IDepartmentModel> { 
    return super.get<IDepartmentModel>(departmentId); 
} 

Anmerkung: die Superklasse Angular Http nutzt, die gut getestet und bestätigt richtig zu funktionieren. Das Problem mit der Logik ist hier nicht ...

Relevante Komponentenmethoden: Anmerkung der departmentService.get() aufrufen, das ist innerhalb der forEach mehrmals aufgerufen wird.

setInitialDepartmentsAssignedGridData(): void { 
    this.settingsForDropdownSelectedCompanyId = this.userForm.get('defaultCompany').get('defaultCompanyId').value; 

    let departments: IDepartmentModel[] = []; 

    this.userService.user.getValue() //confirmed: valid user is being pulled back from the userService (logic is fine here..) 
     .userCompanies.find(comp => comp.companyId === this.settingsForDropdownSelectedCompanyId) // getting a valid match here (logic is fine here..) 
     .departmentIds.forEach(deptId => this.departmentService.get(deptId).first().subscribe(dept => { // getting a valid department back here (logic is fine here...) 
      departments.push(dept); // HERE LIES THE PROBLEM 
    })); 

    this.setDepartmentsAssignedRowData(departments); 
} 

setDepartmentsAssignedRowData(departments: IDepartmentModel[]): void { 
    console.log('setDeptAssignedRowData called'); // confirmed: method is getting called... 
    console.log(departments); // confirmed: fully-composed collection of departments logged to the console... 

    departments.forEach(dept => { 
     console.log(dept); 
    }); // Y U NO WORK!? 

    departments.map((department) => { 
     console.log(department); // Y U NO WORK? 
     this.departmentAssignedRowData.push({ 
      departmentId: department.id, 
      departmentName: department.name 
     }); 
    }); 

    this.departmentAssignedGridOptions.api.setRowData(this.departmentAssignedRowData); 
} 

Das Problem ist, obwohl das, was an der Konsole angemeldet ist immer ein vollständig komponierten Array abteilungs Objekte, ist es nicht wirklich „da“ ist; Was an setDepartmentsAssignedRowData übergeben wird, ist ein leeres Array.

Ich bin sicher, was passiert, ist, dass die asynchronen Operationen nicht abgeschlossen sind, bevor das Departments-Array an die zweite Methode übergeben wird. Einiges von dem, was ich online gelesen habe, sagt forkJoin, aber ich kann nicht sehen, wie das in diesem Zusammenhang aussehen wird. Ich habe auch gelesen concatMap kann funktionieren, aber wieder in diesem Zusammenhang, ich bin mir nicht sicher, wie diese Arbeit zu machen ...

In diesem Zusammenhang, wie kann ich RxJS nutzen, um sicherzustellen, dass die beabsichtigte, voll komponierte Abteilungen Array ist wirklich bereit, übergeben werden?

danke für jede Einsicht, die Sie zur Verfügung stellen können. Hilfe wird sehr geschätzt!

Antwort

1

Sie sind richtig, müssen Sie forkJoin

let observableArray = this.userService.user.getValue() 
    .userCompanies.find(comp => comp.companyId === this.settingsForDropdownSelectedCompanyId) 
    .departmentIds.map(deptId => this.departmentService.get(deptId)) // map is building out an array of observables 

Dies wird eine Reihe von HTTP-Anforderung Observablen, die Sie parallel machen wollen. Jetzt können Sie dieses Array an forkJoin übergeben.

Observable.forkJoin(...observableArray) 

Die Rückkehr von forkJoin wird ein Array von Ergebnissen aus observableArray sein. forkJoin nicht, bis alle der Observablen in der Folge auf den nächsten Operator emittieren in observableArray abgeschlossen haben (so, wenn alle der HTTP-Anfragen beendet haben)

So ganz der Code

let observableArray = this.userService.user.getValue() 
    .userCompanies.find(comp => comp.companyId === this.settingsForDropdownSelectedCompanyId) 
    .departmentIds.map(deptId => this.departmentService.get(deptId)); 

Observable.forkJoin(...observableArray).subscribe(res => { 
    // res = [resId0, resId1, resId2, ..., resIdX]; 
}); 

sein

Sie haben erwähnt, dass Sie das Ergebnis an einen anderen Operator übergeben haben. Wenn es sich bei diesem Operator um eine weitere HTTP-Anfrage handelt, bei der Sie ein Datenfeld übergeben (von forkJoin), können Sie den Operator flatMap verwenden.

Observable.forkJoin(...observableArray) 
    .flatMap(res => { 
     return this.otherApi(res); 
    }) 
    .subscribe(res => { 
     // res is the result of the otherApi call 
    }); 

flatMap ketten Sie Ihre API-Anfragen zusammen. Also insgesamt, was passiert ist

  1. Lauf Array von Observablen parallel
  2. einmal abgeschlossen ist, laufen zweite api (otherApi)
+0

Awesome, werde ich diesem einen Versuch geben; Danke! Eine Frage: benötigt ForkJoin nicht mehr als einen Parameter? Ich würde nur ObservableArray und nichts anderes zu Observable.forkJoin passieren? –

+0

@SteveBoniface forkJoin nimmt auch einen "Selektor" -Parameter, bei dem es sich um eine Funktion handelt, die das Ergebnis modifiziert. Aber es ist optional und wird in diesem Fall nicht benötigt. Es ist eher eine organisatorische Sache. Sie können die gleiche Änderung im nächsten Operator vornehmen – LLai

+1

Oh, sicher. Ich werde das für diesen Kontext nicht brauchen. Ich habe Ihren Vorschlag ausprobiert (wird meinen ersten Beitrag bearbeiten, um die fertige Lösung zu sehen. Ich kann Ihnen nicht genug für Ihre Einsicht danken. Das hat mir Kopfschmerzen erspart, die jetzt 2-3 Tage gedauert haben :). Kudos! Vielen Dank –

Verwandte Themen