с полезными примерами!

В приложении Angular весь жизненный цикл компонентов управляется Angular, от создания до уничтожения. И он предоставляет нам доступ к хукам жизненного цикла, что позволяет нам действовать в ключевые моменты жизненного цикла компонента.

Чтобы использовать эти ловушки, мы должны сообщить Angular, что мы хотим реализовать желаемый интерфейс ловушки. Angular проверяет классы компонентов и вызывает hook методы, если они определены. Ниже показано, как выглядит этот синтаксис (для OnInit):

export class MyComponent implements OnInit {
    constructor() { }
    ngOnInit() {
        // Insert Logic Here!
    }
}

Это список предоставленных Angular хуков, которые вызываются именно в этом порядке, и мы рассмотрим каждый из них сразу после:

╔════════════════════════════╗
║   lifecycle hooks          ║
╠════════════════════════════╣
║   ngOnChanges()            ║
╠════════════════════════════╣
║   ngOnInit()               ║
╠════════════════════════════╣
║   ngDoCheck()              ║
╠════════════════════════════╣
║   ngAfterContentInit()     ║ 
╠════════════════════════════╣
║   ngAfterContentChecked()  ║  
╠════════════════════════════╣
║   ngAfterViewInit()        ║
╠════════════════════════════╣
║   ngAfterViewChecked()     ║
╠════════════════════════════╣
║   ngOnDestroy()            ║
╚════════════════════════════╝

ngOnChanges

Этот метод вызывается один раз при создании компонента, а затем каждый раз обнаруживаются изменения в одном из свойств input компонента. Он получает объект SimpleChanges в качестве параметра, который содержит информацию о том, какое из свойств input было изменено (если их больше одного), а также его текущее и предыдущее ценности.

export class MyComponent implements OnChanges {
    ngOnChanges(changes: SimpleChanges) {
        // Insert Logic Here!
    }
}

Замечания: если значение не было установлено для свойства ввода, его значение по умолчанию устанавливается как строка «CD_INIT_VALUE».

Это одна из ловушек жизненного цикла, которая может пригодиться в нескольких случаях использования. Это очень полезно, если вам нужно обработать какую-либо конкретную логику в компоненте на основе полученного свойства input.

Допустим, у вас есть компонент, который отображает информацию о пользователях и получает этот объект UserInfo в качестве входного параметра. Ниже приведен пример использования ngOnChanges в этом компоненте для добавления логики для обработки изменений в свойстве UserInfo.

export class UserInfoComponent implements OnChanges {
  @Input userInfo: UserInfo;
  ngOnChanges(changes: SimpleChanges) {
    const previousValue = changes['userInfo'].previousValue;
    const currentValue = changes['userInfo'].currentValue;
    // Your Logic!
  }
}

ngOnInit

Этот метод вызывается только один раз в течение жизненного цикла компонента, после первого вызова ngOnChanges . На этом этапе в рамках этого метода у вас может быть доступ не только к свойствам с привязкой к данным, но и к свойствам ввода компонента .

export class MyComponent implements OnInit {
  ngOnInit() {
      // Insert Logic Here!
  }
}

Это одна из наиболее часто используемых ловушек жизненного цикла в Angular. Здесь вы можете установить запросы к серверу для загрузки содержимого, возможно, создать FormGroup для формы, которая будет обрабатываться этим компонентом, установить подписки и многое другое. По сути, здесь вы можете выполнить любую инициализацию вскоре после создания компонента.

Допустим, у вас есть компонент формы регистрации, и вы хотите создать форму на основе полей, полученных с сервера, которые выбираются в соответствии с типом пользователя. Ниже приведен пример того, как вы можете использовать ngOnInit для этого.

export class RegisterFormComponent implements OnInit {
  public formGroup: FormGroup;
  private _userType: UserTypeEnum;
  constructor(
    private _activatedRoute: ActivatedRoute,
    private _myService: MyService
  ) {
    this._userType =
      this._activatedRoute.snapshot.paramMap.get('userType');
  }
  ngOnInit() {
    this._myService.getFormFieldsByType(
      this._userType
    ).subscribe((response) => {
      this.formGroup = this._createForm(
        response.data
      );
    }, (error) => console.error( error ) );
  }
  private _createForm(formFields: Array<FormFields>): FormGroup {
    // FormGroup Creation Logic!
  }
}

Наблюдения: Конструктор против NgOnInit?

Вероятно, вы задались вопросом, зачем размещать логику инициализации внутри ngOnInit, если вы можете сделать это в конструкторе класса . Что ж, в основном конструктор лучше всего использовать для внедрения зависимостей, а нашу логику инициализации следует поместить в ngOnInit.

Это потому, что механизм Javascript обрабатывает конструктор, а не Angular. И это одна из причин почему был создан хук ngOnInit, который вызывается Angular и становится частью жизненного цикла компонента, которым он управляет. Кроме того, конечно, из-за того, что вы еще не можете получить доступ к свойствам ввода компонента в конструкторе.

ngDoCheck

Эту ловушку можно интерпретировать как «расширение» ngOnChanges. Вы можете использовать этот метод для обнаружения изменений, которые Angular не может или не может обнаружить. Он вызывается при обнаружении каждого изменения сразу после хуков ngOnChanges и ngOnInit.

export class MyComponent implements DoCheck {
  ...
  private _currentValue;
  private _previousValue;
  public changeDetected: boolean = false;
  ...
  ngDoCheck() {
    if (this._previousValue !== this._currentValue) {
      this.changeDetected = true;
      // Insert Logic Here
    }
  }
}

Замечания: этот хук действительно дорогостоящий, так как он вызывается очень часто; после каждого цикла обнаружения изменений, независимо от того, где произошло изменение. Поэтому при его использовании следует соблюдать осторожность, чтобы не повлиять на взаимодействие с пользователем.

AfterContent и AfterView

Прежде чем мы поговорим об этих хуках, сначала нам нужно понять, к чему они относятся:

«Перехватчики AfterContent касаются ContentChildren, дочерних компонентов, которые Angular проецировал в компонент.

Перехватчики AfterView относятся к ViewChildren, дочерним компонентам, теги элементов которых появляются внутри шаблона компонента ».

Чтобы проиллюстрировать, о чем мы говорим, предположим, что у нас есть компонент ниже, который имеет и ContentChild, и ViewChild. У нас был бы тег ng-content, который будет отображать контент, переданный от родителя, и ссылку на div ViewChild (, который мы назвали оболочкой).

@Component({
  selector: 'my-component',
  template: `
    <div #wrapper >
      <ng-content></ng-content>
    </div>`
})
export class MyComponent implements {
  @ViewChild('wrapper') wrapper: ElementRef;
  @ContentChild('content') content: ElementRef;

}

ngAfterContentInit

Этот метод вызывается только один раз в течение жизненного цикла компонента, после первого ngDoCheck. В этом хуке мы впервые получаем доступ к ElementRef ContentChild после создания компонента; после того, как Angular уже спроецировал внешний контент в представление компонента.

@Component({
  selector: 'my-component',
  template: `
    <div>
      <ng-content></ng-content>
    </div>`
})
export class MyComponent implements AfterContentInit {
  @ContentChild('content') content: ElementRef;

  ngAfterContentInit() {
    // Now we have access to 'this.content'!
    // Insert Logic Here!
  }
}

ngAfterContentChecked

Этот метод вызывается один раз в течение жизненного цикла компонента после ngAfterContentInit, а затем после каждого последующего ngDoCheck. Он вызывается после того, как Angular уже проверил содержимое, проецируемое в компонент в текущем цикле дайджеста.

@Component({
  selector: 'my-component',
  template: `
    <div>
      <ng-content></ng-content>
    </div>`
})
export class MyComponent implements AfterContentChecked {
  @ContentChild('content') content: ElementRef;

  ngAfterContentChecked() {
    // We have access to 'this.content'!
    // Content has already been checked!
    // Insert Logic Here!
  }
}

ngAfterViewInit

Этот метод вызывается только один раз в течение жизненного цикла компонента после ngAfterContentChecked. В этом хуке мы впервые получаем доступ к ElementRefs из ViewChildren после создания компонента; после того, как Angular уже скомпоновал представления компонента и его дочерние представления.

@Component({
  selector: 'my-component',
  template: `
    <div #wrapper >
      ...
    </div>`
})
export class MyComponent implements AfterViewInit {
  @ViewChild('wrapper') wrapper: ElementRef;

  ngAfterViewInit() {
    // Now we have access to 'this.wrapper'
    // Insert Logic Here!
  }

}

Этот перехватчик весьма полезен, когда вам нужно загрузить контент в ваше представление, которое зависит от его компонентов; например, когда вам нужно установить видеопроигрыватель или создать диаграмму из элемента холста. Ниже приведен пример того, как можно установить диаграмму с помощью ловушки ngAfterViewInit.

@Component({
  selector: 'my-component',
  template: `
    <div>
      <canvas id="myCanvas" ></canvas>
    </div>`
})
export class MyComponent implements AfterViewInit {
  ngAfterViewInit() {
    // Now we can get the canvas element by its id
    // in order to create the chart
    this.chart = new Chart('radarCanvas', {
      ...
    });
  }
}

ngAfterViewChecked

Этот метод вызывается один раз после ngAfterViewInit, а затем после каждого последующего ngAfterContentChecked. Он вызывается после того, как Angular уже проверил представления компонента и его дочерние представления в текущем цикле дайджеста.

@Component({
  selector: 'my-component',
  template: `
    <div #wrapper >
      ...
    </div>`
})
export class MyComponent implements AfterViewChecked {
  @ViewChild('wrapper') wrapper: ElementRef;

  ngAfterViewChecked() {
    // Now we have access to 'this.wrapper'!
    // View has already been checked!
    // Insert Logic Here!
  }
}

Наблюдения: ExpressionChangedAfterItHasBeenCheckedError

«Правило однонаправленного потока данных Angular запрещает обновление представления после его создания. Оба этих хука (AfterViewInit и AfterViewChecked) срабатывают после составления представления компонента ».

obs выше, процитированный из Angular Docs, это то, что вызывает знаменитую ошибку ExpressionChangedAfterItHasBeenCheckedError. Понимание того, когда использовать эти хуки жизненного цикла, поможет вам избежать этого. Если вы хотите узнать больше об этом и о том, как работает обнаружение изменений в Angular, вы можете прочитать эту статью.

ngOnDestroy

Наконец, этот метод вызывается только один раз в течение жизненного цикла компонента, прямо перед тем, как Angular уничтожит его . Здесь вы должны сообщить остальной части вашего приложения, что компонент уничтожается, на случай, если с этой информацией нужно выполнить какие-либо действия.

export class MyComponent implements OnDestroy {
  ngOnDestroy() {
      // Insert Logic Here!
  }
}

Также здесь вы должны разместить всю свою логику очистки для этого компонента. Например, здесь вы можете удалить любую информацию localstorage и, что наиболее важно, отменить подписку observables / отсоединить обработчики событий / остановить таймеры и т. д. чтобы избежать утечки памяти.

export class MyComponent implements OnDestroy {
  private _mySubject: Subject<string> = new Subject();
  ...
  ngOnDestroy() {
    localStorage.removeItem('storageKey');
    this._searchSubject.unsubscribe();
  }
}

Наблюдения: ngOnDestroy не вызывается, когда пользователь обновляет страницу или закрывает браузер. Итак, если вам нужно обработать некоторую логику очистки и в этих случаях, вы можете использовать дескоратор HostListener, как показано ниже:

  @HostListener(‘window:beforeunload’)
  ngOnDestroy() {
     // Insert Logic Here!
  }

Понимание ловушек жизненного цикла Angular, их целей и времени их вызова может быть очень полезным при создании приложений Angular. Поэтому важно знать, как они работают и чего с их помощью можно достичь, чтобы иметь возможность применять их всякий раз, когда они могут понадобиться.

Надеюсь, это поможет! 😉

Использованная литература:

Https://angular.io/guide/lifecycle-hooks

Https://hackernoon.com/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error-e3fd9ce7dbb4