I am experiencing some issues with a tricky asynchronous request I am trying to perform in Angular. I am displaying a user interface with specific information condensed in a table. The table contains headers that are loaded from a backend using a http.get method provided by the HttpClient module. I call this method in the ngOnInit() method. Occasionally, it occurs that the table's headers are not loaded before the table is displayed. How can this problem be avoided?
Some information to the implementation:
ngOnInit(): void {
this.toastService.reset();
this.load();
}
load(): void {
const workflows = this.getWorkflows();
const headers = this.getHeaders();
forkJoin([workflows, headers]).subscribe((results) => {
this.setWorkflows(results[0]);
this.setHeaders(results[1]);
}, (error) => {
console.error(`error = ${error}`);
});
}
getWorkflows(): Observable<VisibleWorkflow[]> {
return this.workflowService.getWorkflows().pipe(
map((response) => {
return this.workflows = response;
})
);
}
setWorkflows(workflows: VisibleWorkflow[]): void {
this.workflows = workflows.map((workflow) => {
return {...workflow};
});
}
getHeaders(): Observable<VisibleHeader[]> {
return this.headerService.getHeaders().pipe(
map((response) => {
return this.headers = response;
})
);
}
setHeaders(headers: VisibleHeader[]): void {
this.headers = headers;
}
Thanks for your time!
CodePudding user response:
The issue you're facing results from the point that you're assigning the values to this.workflows and this.headers twice. At first, you're assigning a value inside of the RxJS.map operators and after that in the subscription of your forkJoin.
Since forkJoin only emits the values as soon every Observable completes you should assign the values only in the subscription and not in RxJS.map.
So, your code would look something like following:
load(): void {
const workflows = this.getWorkflows();
const headers = this.getHeaders();
forkJoin([workflows, headers]).subscribe(
// I used array deconstruction here because I prefer that over hard coded indexes
([wfs, heads]) => {
this.setWorkflows(wfs);
this.setHeaders(heads);
},
(error) => {
console.error(`error = ${error}`);
}
);
}
getWorkflows(): Observable<VisibleWorkflow[]> {
return this.workflowService.getWorkflows();
}
getHeaders(): Observable<VisibleHeader[]> {
return this.headerService.getHeaders();
}
Like this you're only assigning the values as soon as both requests have been completed, so you shouldn't have a timing issue anymore.
You could also consider to hide your table as long as both variables aren't set:
<ng-container *ngIf="headers?.length && workflows">
<!-- Your table here -->
</ng-container>
This would also solve a timing issue, if existing ;)
CodePudding user response:
You can use combineLatest
combineLatest: Whenever any input Observable emits a value, it computes a formula using the latest values from all the inputs, then emits the output of that formula. https://rxjs.dev/api/index/function/combineLatest
