Reputation: 87
I am using Angular6 and am stuck in a situation where I have a parent component (say parent) which has 3 child components (say child1, child2, child3). parent-component.html looks like this:
<div>
<child1></child1>
<child2></child2>
<child3></child3>
</div>
All the child components call their services (which in turn fetch data from http APIs) and load their own data. All the components have different data load time (say child1 has 1 sec, child2 has 2 sec and child3 has 3 sec).
I want to execute a function in paren when ALL three child components have loaded their data that they got from service.
Upvotes: 1
Views: 4504
Reputation: 31115
I assume all the API calls are made from the same service for simplicity. If not you could simply create a service common to all the components and use it. The basic mechanism will stay the same.
You could create a multiple Subjects that feed into one main Subject that the parent component can feed on using zip
function to know if the background calls are over. Try the following
Common Service
import { zip } from 'rxjs';
@Injectable()
export class ApiService {
private apiOneStatusSource = new Subject<boolean>();
private apiTwoStatusSource = new Subject<boolean>();
private apiThreeStatusSource = new Subject<boolean>();
private apisStatusSource = new Subject<boolean>();
private apiOneStatus$ = this.apiOneStatusSource.asObservable();
private apiTwoStatus$ = this.apiOneStatusSource.asObservable();
private apiThreeStatus$ = this.apiOneStatusSource.asObservable();
private apisStatus$ = this.apisStatusSource.asObservable();
constructor() {
zip(this.apiOneStatus$, this.apiTwoStatus$, this.apiThreeStatus$).subscribe( // <-- use `zip` to check if all the observables have been emitted
statuses => {
if (statuses.every(status => status === true)) {
this.apisStatusSource.next(true);
}
}
);
}
public setApiOneStatus(status: boolean) {
this.apiOneStatusSource.next(status);
}
public setApiTwoStatus(status: boolean) {
this.apiTwoStatusSource.next(status);
}
public setApiThreeStatus(status: boolean) {
this.apiThreeStatusSource.next(status);
}
public getApisStatus() {
return this.apisStatus$;
}
}
Parent component
ngOnInit() {
this.apiService.getApisStatus.subscribe(
status => {
// api from all 3 child compnents have returned values - proceed further
}
);
}
Child components
this.apiService.componentApiCall().subscribe(
response => {
// handle response
this.apiService.setApiOneStatus(true); // <-- set corresponding status here: setApiTwoStatus for child 2 and so on...
},
error => {
// handle error
this.apiService.setApiOneStatus(false); // <-- set corresponding status here: setApiTwoStatus for child 2 and so on...
);
Upvotes: 0
Reputation: 5270
You could add an output method to children components in order to emit when data is loaded, something like this,
child.ts
import { Component, Output, EventEmitter } from '@angular/core';
import { Observable } from 'rxjs';
@Component({
selector: 'app-childx',
templateUrl: './childx.component.html',
styleUrls: ['./childx.component.scss']
})
export class Childx {
data: Data;
// output
@Output() dataLoaded = new EventEmitter<string>();
constructor() { }
ngOnInit() {
// emit after loading data
}
emit() {
this.dataLoaded.emit('Childx');
}
}
parent.html
<div>
<child1 (dataLoaded)="dataLoaded($event)"></child1>
<child2 (dataLoaded)="dataLoaded($event)"></child2>
<child3 (dataLoaded)="dataLoaded($event)"></child3>
</div>
parent.ts
childrenDataLoadedCount = 0;
dataLoaded(child: string) {
childrenDataLoadedCount++;
if (childrenDataLoadedCount === 3) {
// here all children load data
}
}
I put a really simple logic just to exemplify, you will have to add something according to your case. I also use a string as the emit parameter of the children, it could be anything or nothing, in this simple example it should be probably nothing, but I added to just show a parameter that could be useful.
Upvotes: 1