Suppose I have a parent component with two or more child components that both asynchronously fetch data from an API. I want to show a loading animation in the parent component until both child components are finished fetching the data. I had the idea to create a property loading: number
on a shared service and increment/decrement the value from the child components. The parent component would have a *ngIf="loading === 0"
to conditionally show the loading animation. However, as some might already suspect, this leads to changedAfterChecked error since I am updating the parent view from a child component.
I have already managed to get it working by manually updating the view but this is not an elegant solution. Hence, I wanted to ask if there is any other way or pattern to achieve the beforementioned.
CodePudding user response:
You can use @Output
to notify parent it child's current state. This will correctly trigger change detection withing angular component lifecycle. For example
@Component({
selector: 'app-child-component',
templateUrl: './child-component.component.html',
styleUrls: ['./child-component.component.css'],
})
export class ChildComponentComponent implements OnInit {
@Output()
state = new EventEmitter<boolean>();
constructor() {}
ngOnInit() {
let s = true;
setInterval(() => {
this.state.next(s);
s = !s;
}, 500);
}
}
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
name = 'Angular ' VERSION.major;
childStatus=false;
}
And parent template:
Child state: {{ childStatus }}
<app-child-component (state)="childStatus = $event"> </app-child-component>
https://stackblitz.com/edit/angular-ivy-hfhqap?file=src/app/app.component.html
CodePudding user response:
You can use combinelatest in the parent and @output in the children to emit data to, the parent from children.
Be aware that combineLatest will not emit an initial value until each observable emits at least one value.
In your childreen component
@Output() someEvent = this.someSource$.pipe(
distinctUntilChanged(),
debounceTime(this.fetchMyData)
);
In your parent
// html file
...
<child (someEvent)="event1$"></child>
<child (someEvent)="event2$"></child>
...
// TS file
// Hide or show loader
loader = false;
...
combineLatest(event1$, event2$).subscribe(
([eventOne, eventTwo]) => {
if(!!eventOne && !!eventTwo) {
this.loader = false;
}
}
);
combinelatest : https://www.learnrxjs.io/learn-rxjs/operators/combination/combinelatest