Angular 2 how to make child component wait for async data to be ready

Angular

Angular 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:

  1. Put an *ngIf in parent. Only render child when parent's items is ready.
<div *ngIf="items">
   <child [items]="items | async">
</div>
  1. Separate your input getter setter in child. Then act whenever the value is set, you can use RxJS BehaviorSubject 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);
    })
}
  1. 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
     }    
    
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAllen ZhangView Question on Stackoverflow
Solution 1 - AngularChybieView Answer on Stackoverflow
Solution 2 - AngularDanielWawView Answer on Stackoverflow
Solution 3 - AngularMiladView Answer on Stackoverflow