AB HAT
AB HAT

Reputation: 87

Angular: Call parent component function when all child components complete data load

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

Answers (2)

Barremian
Barremian

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

cabesuon
cabesuon

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

Related Questions