As far as I understood, there are 2 ways of distributing my data from external asynchronous web API calls to my components:
I have a root app component with some dynamically created divs that are showing content and controls to switch back and forward to see different data. That forward and backward switching is implemented by an index number that my controls are controlling.
My first approach was to split the object data in my root component and pass it to my custom components. Something like this:
<controls></controls>
<div class="data-container" *ngFor="let data of data; let i = index">
<div *ngIf="i === contentIndex">
<general-information [generalInfo]="data.generalInformation"></general-information>
<status-lights-container [statusInfo]="data.statusInformation"></status-lights-container>
<textfields [textfields]="data.textFields"></textfields>
</div>
</div>
I created a service which I call in my app component, wait for the promise and then iterate with *ngFor through my array of object data. I am passing the data to my custom components and pass the needed data. Like this:
import { Data } from './models/Data';
import { StatusService } from './services/status.service';
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
data: Data[];
constructor(private ss: StatusService) {
}
ngOnInit(): void {
this.getData();
}
getData(): void {
this.ss.getStatusData().then(status => {
this.status = status;
});
}
}
As you can see, my app root component never used any of the data which is now cached in an object that is only used for creating the HTML and passing the data to my other components (something that my service should do). Also passing data from component to component is laborious.
I have also the problem, that my <status-lights-container>
is only a container, which also splits the data and passes it to other custom components. So I have even more data which is not really used and cached.
My second approach is to call the service function to get the data and save it in a variable in the service. Then pass the length of my data array to my root app component and create this count of component elements in my HTML.
<div class="content-container" *ngFor="let index of arrayLength; let i = index">
<div *ngIf="i === contentIndex">
<general-information [index]="index"></general-information>
<status-lights-container [index]="index"></status-lights-container>
<textfields [index]="index"></textfields>
</div>
</div>
getData(): void {
this.ss.getStatusData().then(length => {
this.arrayLength = length;
});
}
Now I would load the data from the data array that is saved in my service into my components according to the contentIndex they are receiving.
Begin of questions
At the moment of ngOnInit()
is getting fired where I am starting the web API, how do I wait for the asynchronous call getting finished in my nested components so that they know that the data is finished loading?
If I switch to another data-container
div in my view I just change the contentIndex
in my service from my <controls>
component. But how is my data-container
getting informed about this kind of change?
Is this generally a good approach? Any other suggestions?
Things I want to achieve and avoid
Edit: This is my status.service.ts after first suggestions in the answers:
import pnp from "sp-pnp-js";
import { BehaviorSubject, Observable } from 'rxjs/Rx';
import { Http } from "@angular/http";
import { Injectable } from '@angular/core';
import { Status } from './../models/status';
import { STATUS } from "app/models/mock-status";
@Injectable()
export class StatusService {
status: BehaviorSubject <Status[]> = new BehaviorSubject(null);
constructor(private http: Http) { }
getStatusData(): Observable<Status[]> {
return this.http.get("app/models/mock-status")
.map((res: Status[]) => res.json() as Status[])
.catch((err: Response) => Observable.throw(console.log(err)));
}
setStatus(status: Status[]): void {
this.status.next(status);
}
getStatus(): Observable <Status[]> {
return this.status.asObservable();
}
}
StatusService
@Injectable()
export class StatusService {
status: BehaviorSubject< class[] > = new BehaviourSubject(null);
constructor(private http: Http) {}
getStatusData(): Observable < class[] > {
return this.http.get(parameters)
.map((res: class[]) => res.json() as class[])
.catch((err: Response) => Observable.throw(console.log(err)));
}
setStatus(status: class[]): void {
this.status.next(status);
}
getStatus(): Observable < class[] > {
return this.status.asObservable();
}
}
AppComponent
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
data: Data[];
constructor(private ss: StatusService) {}
ngOnInit(): void {
this.getData();
}
getData(): void {
this.ss.getStatusData().subscribe((status: class[]) => {
this.ss.setStatus(status);
});
}
}
This would start fetching the values from the service and store it in the observable. All you have to do is Subscribe
to the data from other child components like getData()
do and when the Behaviour subject has values it will push them to components that are subscribed to it. As a added bonus, it will store the last value for later so that if a component subscribes to is afterwards it also receives the data.