I am trying to pass data from a parent to its child component in Angular 17. The data is retrieved from the back-end through an API that gets an array of items.
In my parent component, items$ is a subject:
items$ = Subject<Array<Item>> = new Subject();
I subscribe it to the method of my service that retrieves all items from the backend:
ngOnInit(){
this.itemService.getItems().subscribe(this.items$);
}
In my parent.html template, this code does display that there are items:
@if(items$ | async){
"There are items!"
}@else{
"There aren't any items"
}
So I then call my child component:
@if(items$ | async){
<app-child [items$]=items$>
}
This is what my child.component.ts looks like:
export class ChildComponent implements OnInit{
@Input() items$: Observable<Array<Item>>;
ngOnInit(){
this.items$.subscribe();
}
}
An child.component.html:
@if(items$ | async){
"There are items in the child component!"
}@else{
"There aren't any items in the child component"
}
Well, it displays that there aren't any items in the child component.
I know I don't need to subscribe in the onInit() method of my child component, but I really just wanted to make sure that I am indeed subscribed to my multicast observable.
Also, interestingly, when I take my child component out of the @if condition in parent.html, the items are displaying!
Any idea why? And how can I condition the display of my child component to the presence of items?
You can convert the Subject
to a BehaviorSubject
, so that it maintains state (the array). Prefer subject when you want to trigger emissions for events (Event Bus) not holding state.
Also you should write a callback inside the subscribe that calls the next
method of the BehaviorSubject
with the data from the API.
itemService = inject(ItemService);
items$: BehaviorSubject<Array<any> | null> =
new BehaviorSubject<Array<any> | null>(null);
ngOnInit() {
this.itemService.getItems().subscribe((data: any) => {
this.items$.next(data);
});
}
import { Component, inject, Injectable, Input, OnInit } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { Observable, of, Subject, BehaviorSubject } from 'rxjs';
import { CommonModule } from '@angular/common';
@Injectable({ providedIn: 'root' })
export class ItemService {
getItems() {
return of([{ test: 1 }]);
}
}
@Component({
selector: 'app-child',
imports: [CommonModule],
template: `
@if(items$ | async) {
"There are items in the child component!"
} @else {
"There aren't any items in the child component"
}
`,
})
export class ChildComponent implements OnInit {
@Input() items$!: Observable<Array<any> | null>;
ngOnInit() {
this.items$.subscribe();
}
}
@Component({
selector: 'app-root',
imports: [CommonModule, ChildComponent],
template: `
@if(items$ | async){
"There are items!"
}@else{
"There aren't any items"
}
<hr/>
@if(items$ | async) {
<app-child [items$]="items$"/>
}
`,
})
export class App {
itemService = inject(ItemService);
items$: BehaviorSubject<Array<any> | null> =
new BehaviorSubject<Array<any> | null>(null);
ngOnInit() {
this.itemService.getItems().subscribe((data: any) => {
this.items$.next(data);
});
}
}
bootstrapApplication(App);