Angular 2 как заставить дочерний компонент ждать, пока данные asyn будут готовы

Я передаю async данные из родительского компонента в дочерний компонент. И дочерний компонент должен знать длину данных, чтобы что-то сделать.

Как проблема заключается в том, что дочерний компонент не может использовать крюк Oninit для выполнения работы, потому что данные в настоящее время недоступны. Итак, как мне это сделать?

Код родительского компонента выглядит так:

@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
            );
    }

}

И дочерний компонент выглядит следующим образом:

@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;
    }
}

Какова наилучшая практика для этого?

Ответ 1

Есть три способа сделать это:

  1. Поместите *ngIf в родительский. Отображать дочерний items только тогда, когда родительские items готовы.

    <div *ngIf="items">
       <child [items]="items | async">
    </div>
    
  2. Разделите ваш входной setter getter на ребенка. Затем действуйте всякий раз, когда значение установлено, вы также можете использовать RxJS BehaviorSubject.

    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);
        })
    }
    
  3. Делайте это во время ngOnChanges ребенка. Обратитесь сюда, например. https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html#!#onchanges

Ответ 2

Вы можете использовать сеттер:

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;
    }
}



Между прочим, я чувствую, что ваш родительский компонент тоже не прав, это должно быть:

@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
     }    

}

Ответ 3

Более простое решение:

ngOnChanges(changes: SimpleChanges) {
    if (changes['items'].currentValue) {
        this.items = items
    }
}