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 и слушать его там, где вам это нужно

Ответ 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, "Не выполнять второй проход во время обнаружения изменений".