Angular 2 how to make child component wait for async data to be ready
AngularAngular Problem Overview
I'm passing async data from a parent component to a child component. And the child component needs to know the length of the data in order to do something.
How the problem is that the child component can't use the 'Oninit' hook to do work because the data isn't available at this time. So how do I do this?
The parent component code looks like:
@Component({
moduleId: module.id,
selector: 'parent',
template: `<div>
<child [items]="items | async">
</div>`
})
export class Parent implements OnInit {
items: Items[];
constructor(
private itemService: ItemService,
private router: Router
) { }
ngOnInit() {
this.itemService.getItemss()
.subscribe(
items => this.items = items,
error => this.errorMessage = <any>error
);
}
}
And the child component looks like:
@Component({
moduleId: module.id,
selector: 'child',
template: `<div>
<div *ngFor="let itemChunk of itemChunks"></div>
content here
</div>`
})
export class child implements OnInit{
@Input() items: Items[];
itemChunks: Items[][];
ngOnInit() {
this.itemChunks = this.chunk(this.Items);
}
chunk(items: Items[]) {
let result = [];
for (var i = 0, len = items.length; i < len; i += 6) { // this line causes the problem since 'items' is undefined
result.push(items.slice(i, i + 6));
}
return result;
}
}
What is the best practice to handle this?
Angular Solutions
Solution 1 - Angular
There are three ways to do this:
- Put an
*ngIf
in parent. Only render child when parent'sitems
is ready.
<div *ngIf="items">
<child [items]="items | async">
</div>
- Separate your input
getter
setter
in child. Then act whenever the value is set, you can use RxJSBehaviorSubject
also.
private _items = new BehaviorSubject<Items[]>([]);
@Input() set items(value: Items[]) {
this._items.next(value);
}
get items() {
return this._items.getValue();
}
ngOnInit() {
this._items.subscribe(x => {
this.chunk(x);
})
}
- Do it during the
ngOnChanges
of the child. Refer to here for example. https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html#!#onchanges
Solution 2 - Angular
Simpler solution:
ngOnChanges(changes: SimpleChanges) {
if (changes['items'].currentValue) {
this.items = items
}
}
Solution 3 - Angular
You can use a setter :
export class child implements OnInit{
itemChunks: Items[][];
private _items ;
//bellow will get called when ever the items are passed in by the parent component
@Input( 'items' ) set items ( items: Items[] ) {
this._items = items;
this.itemChunks = this.chunk(this._items);
}
chunk(items: Items[]) {
let result = [];
for (var i = 0, len = items.length; i < len; i += 6) { // this line causes the problem since 'items' is undefined
result.push(items.slice(i, i + 6));
}
return result;
}
}
Any by the way, I feel like your parent component is not right as well, it should be :
@Component({
moduleId: module.id,
selector: 'parent',
template: `<div>
<child [items]="items | async">
</div>`
})
export class Parent implements OnInit {
items: Items[];
constructor(
private itemService: ItemService,
private router: Router
) {
this.items = this.itemService.getItemss(); // if getItemss is returning an observable, which I think it does
}
}