Angular 2 крючка жизненного цикла после инициализации всех детей?
Я ищу концепцию для реализации следующего случая:
У меня есть компонент родительского поиска, который имеет некоторые компоненты в качестве дочерних элементов children/content для отображения фасетов и результатов поиска. Теперь я хочу запустить поиск, когда приложение закончит загрузку, чтобы пользователь не увидел пустую страницу.
Теперь моя проблема заключается в том, что я не могу найти крючок жизненного цикла, который бы соответствовал моим потребностям. Границы/результаты поиска подписываются на результаты поиска в соответствующем ngOnInit. Поэтому мне нужен хук, получивший имя после, все дочерние компоненты завершили инициализацию.
Я пробовал следующие перехватчики на родительском компоненте
- ngAfterContentInit: он вызывается до того, как ngOnInit вызывается у детей
- ngAfterViewInit: этот работает, но после того, как результаты поиска возвращают представление о том, что дети обновляются, что приводит к ошибке, так как действия, управляющие представлением, не допускаются в ngAfterViewInit
Любая идея, как решить эту проблему? Мне кажется, что я не понимаю здесь ничего принципиального.
Приветствия
Tom
Ответы
Ответ 1
Я закончил использование hook ngAfterViewInit() следующим образом:
ngAfterViewInit(){
//stuff that doesn't do view changes
setTimeout(_=> this.methodThatKicksOffAnotherRoundOfChanges());
}
Это должно быть безопасно для использования по сравнению с другими вызовами setTimeout, где установлено фактическое время, так как оно должно начинаться мгновенно (и просто влиять на поведение потоков/контекстов)
Ответ 2
Заставить дочерние компоненты испускать событие на их Init и слушать его там, где вам это нужно
Ответ 3
Если у вас также есть контентные дети (transcluded using <ng-content>
), то ngAfterContentInit()
- правильный обратный вызов жизненного цикла.
https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html#!#aftercontent
ngAfterContentInit() {
doSomething();
}
Ответ 4
Кажется, что OP думает, что это не устраивает его потребности или не изящно, но кажется, что ответ Vale Steve является наиболее логичным подходом здесь.
В моем случае мне нужно было подождать, пока будут инициализированы 2 дочерних компонента, которые управляют левой и правой панелями в моем приложении. У меня есть оба дочерних компонента, объявляющих их инициализацию на общую службу:
constructor(
public _navigate: RouteNavigationService // service shared between all child components and parent
){}
ngOnInit() {
this._navigate.setChildInit(this.panel);
}
Общая служба (RouteNavigationService):
private _leftChildInit = new Subject();
private _rightChildInit = new Subject();
leftChildInit$ = this._leftChildInit.asObservable();
rightChildInit$ = this._rightChildInit.asObservable();
setChildInit(panel){
switch(panel){
case 'leftPanel':
console.log('left panel initialized!');
this._leftChildInit.next(true);
break;
case 'rightPanel':
console.log('right panel initialized!');
this._rightChildInit.next(true);
break;
}
}
Затем в моем родительском компоненте я использую метод zip для объединения нескольких наблюдаемых вместе (здесь вы также можете добавить дополнительные дочерние компоненты ) и дождитесь их завершения:
childInitSub: Subscription;
constructor(
public _navigate: RouteNavigationService
) {
this.childInitSub = Observable.zip( // WAIT FOR BOTH LEFT & RIGHT PANELS' ngOnInit() TO FIRE
this._navigate.leftChildInit$,
this._navigate.rightChildInit$).subscribe(data =>
// Both child components have initialized... let go!
);
}
Состояние OP в комментарии
"Мне это не нравится, поскольку каждый раз, когда я добавляю новый компонент, мне нужно дождитесь дополнительного события onInit и должны его реализовать"
Но реалистично все, что вам нужно сделать, это сделать еще один Subject
в вашей службе для нового дочернего компонента ngOnInit и добавить дополнительную строку в методе zp родительского компонента.
Обратите внимание, что принятый ответ здесь, используя ngAfterViewInit(), не делает то же самое, что и выше. В моем случае использование ngAfterViewInit()
создавало ExpressionChangedAfterItHasBeenCheckedError, что выходит за рамки этого вопроса, но служит для демонстрации того, что этот подход на самом деле не делает то же самое.
В отличие от вышеприведенного метода буквально запускается только как прямой результат всех событий вашего дочернего компонента ngOnInit()
, у которых есть.
Ответ 5
Если вашему родительскому компоненту требуется ожидание пользовательского асинхронного поведения для нескольких дочерних компонентов, тогда вот хорошее решение, которое я приготовил.
Любой дочерний компонент, который инициализирует асинхронно, расширяет это:
import {Subject} from 'rxjs/Subject';
import {Observable} from 'rxjs/Observable';
export class AsynchronouslyInitialisedComponent {
loadedState: Subject<boolean> = new Subject<boolean>();
loadedState$ = this.loadedState.asObservable();
constructor() {
}
protected componentLoaded() {
this.loadedState.next(true);
}
}
Пример Детский компонент:
import {Component} from '@angular/core';
import {AsynchronouslyInitialisedComponent} from './../base_component';
@Component({
moduleId: module.id,
selector: 'child'
})
export class Child extends AsynchronouslyInitialisedComponent {
constructor() {
super();
}
ngOnInit() {
//Some async behavoir:
setTimeout(() => {
this.componentLoaded();
}, 10000);
}
}
Теперь все ваши родительские компоненты должны сделать следующее:
import {Component, ViewEncapsulation, ViewChild};
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/observable/zip';
@Component({
moduleId: module.id,
selector: 'parent'
})
export class Parent {
@ViewChild('child') child; //Just use <child #child></child> in your template
constructor() {
}
ngOnInit() {
Observable
.zip(this.child.loadedState$, this.otherChild.loadedState$) //Add as many as you want here...
.subscribe(pair => {
console.log('All child components loaded');
});
}
}
Ответ 6
В режиме dev (по умолчанию) механизм обнаружения изменений выполняет дополнительную проверку дерева компонентов, чтобы убедиться, что вы не манипулируете пользовательским интерфейсом в перехватах. Вот почему вы получаете ошибки.
Если вы уверены, что ваши изменения пользовательского интерфейса в ngAfterViewInit действительны, вызовите enableProdMode() прямо перед загрузкой(). Это сообщает Angular, "Не выполнять второй проход во время обнаружения изменений".