Временные метки жизненного цикла
Angular вызывает методы временных меток жизненного цикла для директив и компонентов при их создании, изменении и уничтожении.
Компонент имеет жизненный цикл, управляемый Angular.
Angular создает его, отображает его, создает и отображает его дочерние элементы, проверяет его при изменении свойств, связанных с данными, и уничтожает его перед удалением из DOM.
Angular предлагает временные метки жизненного цикла, которые обеспечивают возможность просмотреть эти ключевые моменты жизни и возможность действовать при их наступлении.
Директива имеет тот же набор временных меток жизненного цикла, за исключением меток, специфичных для содержимого и представлений компонента.
Содержание
- Обзор временных меток жизненного цикла компонента
- Последовательность жизненного цикла
- Интерфейсы необязательны (технически)
- Другие временные метки жизненного цикла Angular
- Примеры жизненного цикла
- Peek-a-boo: все временные метки
- Наблюдение за OnInit и OnDestroy
- OnChanges
- DoCheck
- AfterView
- AfterContent
Попробуйте пример в режиме реального времени.
Обзор временных меток жизненного цикла компонента
У экземпляров директив и компонентов есть жизненный цикл, когда Angular создает, обновляет и уничтожает их. Разработчики могут воспользоваться ключевыми моментами этого жизненного цикла, реализовав один или несколько интерфейсов временных меток жизненного цикла в библиотеке Angular core.
Каждый интерфейс имеет единственный метод-метку, имя которого является именем интерфейса с префиксом ng. Например, интерфейс OnInit имеет метод-метку ngOnInit(), который Angular вызывает сразу после создания компонента:
peek-a-boo.component.ts (фрагмент)
export class PeekABoo implements OnInit {
constructor(private logger: LoggerService) { }
// implement OnInit's `ngOnInit` method
ngOnInit() { this.logIt(`OnInit`); }
logIt(msg: string) {
this.logger.log(`#${nextId++} ${msg}`);
}
}
Ни одна директива или компонент не будут реализовывать все временные метки жизненного цикла, и некоторые метки имеют смысл только для компонентов. Angular вызывает метод метки директивы/компонента только в том случае, если он определен.
Последовательность жизненного цикла
После создания компонента/директивы путем вызова его конструктора Angular вызывает методы временных меток жизненного цикла в следующей последовательности в определенные моменты:
| Метка | Цель и время |
|---|---|
ngOnChanges() |
Реагировать, когда Angular (пере)устанавливает свойства ввода, связанные с данными. Метод получает объект Вызывается до |
ngOnInit() |
Инициализировать директиву/компонент после того, как Angular впервые отобразит свойства, связанные с данными, и установит свойства ввода директивы/компонента. Вызывается один раз после первого |
ngDoCheck() |
Обнаружение и реагирование на изменения, которые Angular не может или не будет обнаруживать самостоятельно. Вызывается во время каждого запуска обнаружения изменений, сразу после |
ngAfterContentInit() |
Реагировать после того, как Angular спроектирует внешнее содержимое в представление компонента. Вызывается один раз после первого Только для компонента метка. |
ngAfterContentChecked() |
Реагировать после того, как Angular проверит содержимое, спроецированное в компонент. Вызывается после Только для компонента метка. |
ngAfterViewInit() |
Реагировать после того, как Angular инициализирует представления компонента и дочерние представления. Вызывается один раз после первого Только для компонента метка. |
ngAfterViewChecked() |
Реагировать после того, как Angular проверит представления компонента и дочерние представления. Вызывается после Только для компонента метка. |
ngOnDestroy |
Очистка непосредственно перед уничтожением директивы/компонента Angular. Отписаться от наблюдаемых и отсоединить обработчики событий, чтобы избежать утечек памяти. Вызывается непосредственно перед уничтожением директивы/компонента Angular. |
Интерфейсы необязательны (технически)
Интерфейсы являются необязательными для разработчиков JavaScript и TypeScript с чисто технической точки зрения. Язык JavaScript не имеет интерфейсов. Angular не может увидеть интерфейсы TypeScript во время выполнения, потому что они исчезают из транспилированного JavaScript.
К счастью, они не нужны. Вам не нужно добавлять интерфейсы временных меток жизненного цикла в директивы и компоненты, чтобы воспользоваться преимуществами самих меток.
Вместо этого Angular инспектирует классы директив и компонентов и вызывает методы меток если они определены. Angular находит и вызывает методы, такие как ngOnInit(), с интерфейсами или без них.
Тем не менее, рекомендуется добавлять интерфейсы в классы директив TypeScript, чтобы получить преимущества сильной типизации и инструментов для редактирования.
Другие временные метки жизненного цикла Angular
Другие подсистемы Angular могут иметь свои собственные временные метки жизненного цикла помимо этих временных меток компонента.
Библиотеки сторонних производителей также могут реализовывать свои метки, чтобы дать разработчикам больше контроля над тем, как используются эти библиотеки.
Примеры жизненного цикла
Пример в режиме реального времени демонстрирует временные метки жизненного цикла в действии через серию упражнений, представленных как компоненты под управлением корневого AppComponent.
Они следуют общему шаблону: родительский компонент служит стендом для тестирования дочернего компонента, который иллюстрирует один или несколько методов временных меток жизненного цикла.
Вот краткое описание каждого упражнения:
| Компонент | Описание |
|---|---|
| Peek-a-boo |
Демонстрирует все временные метки. Каждый метод метки записывает сообщение в журнал на экране. |
| Шпион |
У директив тоже есть метки жизненного цикла. Директива В этом примере |
| OnChanges |
Посмотрите, как Angular вызывает метку |
| DoCheck |
Реализует метод |
| AfterView |
Показывает, что подразумевает Angular под представлением. Демонстрирует метки |
| AfterContent |
Показывает, как спроектировать внешнее содержимое в компонент и как отличить спроецированное содержимое от дочерних элементов представления компонента. Демонстрирует метки |
| Счетчик |
Демонстрирует комбинацию компонента и директивы, у каждой из которых есть свои собственные метки. В этом примере |
В остальной части этой страницы подробно рассматриваются выбранные упражнения.
Peek-a-boo: все метки
PeekABooComponent демонстрирует все метки в одном компоненте.
Вы редко, если вообще когда-либо, будете реализовывать все интерфейсы таким образом. Peek-a-boo существует для демонстрации того, как Angular вызывает метки в ожидаемой последовательности.
Этот снимок отражает состояние журнала после того, как пользователь нажал кнопку Создать..., а затем кнопку Уничтожить....
Последовательность сообщений журнала соответствует предписанному порядку вызова меток: OnChanges, OnInit, DoCheck (3 раза), AfterContentInit, AfterContentChecked (3 раза), AfterViewInit, AfterViewChecked (3 раза) и OnDestroy.
Конструктор не является собственно меткой Angular. Журнал подтверждает, что свойства ввода (свойство
nameв данном случае) не имеют назначенных значений при создании.
Если бы пользователь нажал кнопку Обновить героя, в журнале было бы показано еще одно OnChanges и еще две тройки DoCheck, AfterContentChecked и AfterViewChecked . Очевидно, что эти три метки срабатывают часто. Держите логику в этих метках как можно более простой!
Следующие примеры сосредоточены на подробностях меток.
Наблюдение за OnInit и OnDestroy
Сделайте подмену с этими двумя метками шпиона, чтобы узнать, когда элемент инициализируется или уничтожается.
Это идеальное задание для директивы-шпиона. Герои никогда не узнают, что за ними наблюдают.
Шутки в сторону, обратите внимание на два ключевых момента:
Angular вызывает методы меток для директив, а также компонентов.
Директива-шпион может дать представление об объекте DOM, который вы не можете изменить напрямую. Очевидно, что вы не можете изменить реализацию
<div>. Вы также не можете изменить компонент третьей стороны. Но вы можете наблюдать за обоими с помощью директивы.
Директива-шпион простая, состоящая почти полностью из меток ngOnInit() и ngOnDestroy() , которые регистрируют сообщения в родительский элемент через введенный LoggerService.
// Spy on any element to which it is applied.
// Usage: <div mySpy>...</div>
@Directive({selector: '[mySpy]'})
export class SpyDirective implements OnInit, OnDestroy {
constructor(private logger: LoggerService) { }
ngOnInit() { this.logIt(`onInit`); }
ngOnDestroy() { this.logIt(`onDestroy`); }
private logIt(msg: string) {
this.logger.log(`Spy #${nextId++} ${msg}`);
}
}
Вы можете применить шпиона к любому элементу, например, к элементу <div>, и он будет инициализирован и уничтожен одновременно с этим элементом.
<div *ngFor="let hero of heroes" mySpy class="heroes">
{{hero}}
</div>
Рождение и смерть каждого шпиона отмечаются рождением и смертью прикрепленного героя <div> с записью в журнале Журнал связей, как показано здесь:
Добавление героя приводит к новому герою <div>. Шпион ngOnInit() регистрирует это событие.
Кнопка Сброс очищает список heroes. Angular удаляет все элементы героя <div> из DOM и одновременно уничтожает их директивы шпиона. Метод шпиона ngOnDestroy() сообщает о его последних мгновениях.
Методы ngOnInit() и ngOnDestroy() играют более важную роль в реальных приложениях.
OnInit()
Используйте ngOnInit() по двум основным причинам:
- Для выполнения сложных инициализаций вскоре после создания.
- Для настройки компонента после того, как Angular установит свойства ввода.
Опытные разработчики согласны, что компоненты должны быть дешевыми и безопасными в создании.
Руководитель команды Angular Миско Хевери объясняет, почему следует избегать сложной логики конструктора.
Не загружайте данные в конструктор компонента. Не нужно беспокоиться о том, что новый компонент будет пытаться связаться с удалённым сервером при создании в ходе тестирования или до принятия решения о его отображении. Конструкторы должны только устанавливать начальные локальные переменные в простые значения.
Загрузка начальных данных компонентом — это хорошая практика в ngOnInit(). Руководства «Путеводитель по героям» (Тур по героям, часть 4) и «Клиент HTTP» (HTTP-клиент) демонстрируют это.
Также помните, что свойства ввода данных, связанные с директивами, не устанавливаются до момента после создания. Это проблема, если вам нужно инициализировать директиву на основе этих свойств. Они будут установлены, когда ngOnInit() будет выполнен.
Метод
ngOnChanges()— ваша первая возможность получить доступ к этим свойствам. Angular вызываетngOnChanges()доngOnInit()и много раз после этого. Он вызываетngOnInit()только один раз.
Вы можете рассчитывать на то, что Angular вызовет метод ngOnInit() вскоре после создания компонента. Именно там должна располагаться сложная логика инициализации.
OnDestroy()
Разместите логику очистки в ngOnDestroy(), логику, которая обязательно должна быть выполнена перед уничтожением директивы Angular.
В это время можно уведомить другую часть приложения о том, что компонент уходит.
Здесь можно освободить ресурсы, которые не будут автоматически удалены сборщиком мусора. Отпишитесь от наблюдаемых значений и событий DOM. Остановить таймеры интервалов. Отменить регистрацию всех обратных вызовов, зарегистрированных этой директивой с глобальными или прикладными службами. В противном случае вы рискуете утечкой памяти.
OnChanges()
Angular вызывает метод ngOnChanges() всякий раз, когда обнаруживает изменения в свойствах ввода компонента (или директивы). Этот пример отслеживает связь OnChanges.
on-changes.component.ts (excerpt)
ngOnChanges(changes: SimpleChanges) {
for (let propName in changes) {
let chng = changes[propName];
let cur = JSON.stringify(chng.currentValue);
let prev = JSON.stringify(chng.previousValue);
this.changeLog.push(`${propName}: currentValue = ${cur}, previousValue = ${prev}`);
}
}
Метод ngOnChanges() принимает объект, который сопоставляет каждое изменённое имя свойства с объектом SimpleChange, содержащим текущие и предыдущие значения свойства. Эта связь перебирает изменённые свойства и записывает их в журнал.
Пример компонента, OnChangesComponent, имеет два свойства ввода: hero и power.
@Input() hero: Hero; @Input() power: string;
Хост OnChangesParentComponent связывается с ними так:
<on-changes [hero]="hero" [power]="power"></on-changes>
Вот пример в действии, когда пользователь вносит изменения.
Записи журнала появляются, когда строковое значение свойства power меняется. Но ngOnChanges не ловит изменения в hero.name. Это неожиданно на первый взгляд.
Angular вызывает связь только при изменении значения свойства ввода. Значение свойства hero — это ссылка на объект героя. Angular не заботится о том, что изменилось собственное свойство name героя. Ссылка на объект героя не изменилась, поэтому с точки зрения Angular изменений нет!
DoCheck()
Используйте связь DoCheck для обнаружения и реагирования на изменения, которые Angular не улавливает самостоятельно.
Используйте этот метод для обнаружения изменения, которое Angular пропустил.
Пример DoCheck расширяет пример OnChanges с помощью следующей связи ngDoCheck():
DoCheckComponent (ngDoCheck)
ngDoCheck() {
if (this.hero.name !== this.oldHeroName) {
this.changeDetected = true;
this.changeLog.push(`DoCheck: Hero name changed to "${this.hero.name}" from "${this.oldHeroName}"`);
this.oldHeroName = this.hero.name;
}
if (this.power !== this.oldPower) {
this.changeDetected = true;
this.changeLog.push(`DoCheck: Power changed to "${this.power}" from "${this.oldPower}"`);
this.oldPower = this.power;
}
if (this.changeDetected) {
this.noChangeCount = 0;
} else {
// log that hook was called when there was no relevant change.
let count = this.noChangeCount += 1;
let noChangeMsg = `DoCheck called ${count}x when no change to hero or power`;
if (count === 1) {
// add new "no change" message
this.changeLog.push(noChangeMsg);
} else {
// update last "no change" message
this.changeLog[this.changeLog.length - 1] = noChangeMsg;
}
}
this.changeDetected = false;
}
Этот код проверяет определённые значения интереса, фиксируя и сравнивая их текущее состояние с предыдущими значениями. Он записывает специальное сообщение в журнал, когда нет существенных изменений в hero или power, чтобы вы могли видеть, как часто вызывается DoCheck. Результаты показательны:
Хотя связь ngDoCheck() может обнаруживать, когда изменилось свойство name героя, это имеет большую цену. Эта связь вызывается чрезвычайно часто — после каждого цикла обработки изменений, независимо от места возникновения изменения. В этом примере она вызывается более двадцати раз, прежде чем пользователь что-либо сделает.
Большинство этих начальных проверок вызываются первым рендерингом Angular не связанных данных в других частях страницы. Даже простое наведение курсора на другую <input> вызывает вызов. Относительно небольшое количество вызовов показывает фактические изменения в релевантных данных. Очевидно, что наша реализация должна быть очень лёгкой, чтобы не страдало пользовательское взаимодействие.
AfterView
Пример AfterView исследует связи AfterViewInit() и AfterViewChecked() Angular вызывает после создания дочерних представлений компонента.
Вот дочернее представление, которое отображает имя героя в <input>:
ChildComponent
@Component({
selector: 'my-child-view',
template: '<input [(ngModel)]="hero">'
})
export class ChildViewComponent {
hero = 'Magneta';
}
Компонент AfterViewComponent отображает это дочернее представление внутри своего шаблона:
AfterViewComponent (template)
template: `
<div>-- child view begins --</div>
<my-child-view></my-child-view>
<div>-- child view ends --</div>`
Следующие связи выполняют действия, основанные на изменяющихся значениях внутри дочернего представления, к которому можно получить доступ только через поиск дочернего представления по свойству, помеченному декоратором @ViewChild.
AfterViewComponent (class excerpts)
export class AfterViewComponent implements AfterViewChecked, AfterViewInit {
private prevHero = '';
// Query for a VIEW child of type `ChildViewComponent`
@ViewChild(ChildViewComponent) viewChild: ChildViewComponent;
ngAfterViewInit() {
// viewChild is set after the view has been initialized
this.logIt('AfterViewInit');
this.doSomething();
}
ngAfterViewChecked() {
// viewChild is updated after the view has been checked
if (this.prevHero === this.viewChild.hero) {
this.logIt('AfterViewChecked (no change)');
} else {
this.prevHero = this.viewChild.hero;
this.logIt('AfterViewChecked');
this.doSomething();
}
}
// ...
}
Придерживайтесь правила однонаправленного потока данных
Метод doSomething() обновляет экран, когда имя героя превышает 10 символов.
AfterViewComponent (doSomething)
// This surrogate for real business logic sets the `comment`
private doSomething() {
let c = this.viewChild.hero.length > 10 ? `That's a long name` : '';
if (c !== this.comment) {
// Wait a tick because the component's view has already been checked
this.logger.tick_then(() => this.comment = c);
}
}
Почему метод doSomething() ожидает момент, прежде чем обновить comment?
Правило однонаправленного потока данных Angular запрещает обновления представления после его составления. Обе эти связи срабатывают после составления представления компонента.
Angular выбрасывает ошибку, если связь обновляет свойство данных компонента comment сразу (попробуйте!).
Связь LoggerService.tick_then() откладывает обновление журнала на один цикл JavaScript браузера, и этого достаточно.
Вот AfterView в действии:
Обратите внимание, что Angular часто вызывает AfterViewChecked(), часто без необходимых изменений. Для избежания проблем производительности пишите компактные методы связей.
AfterContent
Пример AfterContent исследует связи AfterContentInit() и AfterContentChecked() Angular вызывает после проекции внешнего содержимого в компонент.
Проекция содержимого
Проекция содержимого — способ импорта HTML-содержимого извне компонента и вставки этого содержимого в шаблон компонента в указанном месте.
Разработчики AngularJS знают эту технику как транклюзию.
Рассмотрим этот вариант примера AfterView. На этот раз, вместо включения дочернего представления в шаблон, он импортирует содержимое из родителя компонента AfterContentComponent. Вот шаблон родительского компонента:
AfterContentParentComponent (template excerpt)
`<after-content> <my-child></my-child> </after-content>`
Обратите внимание, что тег <my-child> вставлен между тегами <after-content>. Никогда не помещайте содержимое между тегами элемента компонента если не планируется проектировать это содержимое в компонент.
Теперь посмотрите на шаблон компонента:
AfterContentComponent (template)
template: `
<div>-- projected content begins --</div>
<ng-content></ng-content>
<div>-- projected content ends --</div>`
Тег <ng-content> — это заполнитель для внешнего содержимого. Он указывает Angular, куда вставить это содержимое. В этом случае, спроецированное содержимое — это <my-child> от родителя.
Признаки проекции содержимого двойственны:
- HTML между тегами элемента компонента.
- Наличие тегов
<ng-content>в шаблоне компонента.
Связи AfterContent
Связи AfterContent похожи на связи AfterView. Главное различие заключается в дочернем компоненте.
-
Связи AfterView относятся к
ViewChildren, дочерним компонентам, чьи теги элементов появляются внутри шаблона компонента. -
Связи AfterContent относятся к
ContentChildren, дочерним компонентам, спроецированным Angular в компонент.
Следующие связи AfterContent выполняют действия, основанные на изменяющихся значениях в дочернем элементе содержимого, к которому можно получить доступ только через поиск по свойству, помеченному декоратором @ContentChild.
AfterContentComponent (class excerpts)
export class AfterContentComponent implements AfterContentChecked, AfterContentInit {
private prevHero = '';
comment = '';
// Query for a CONTENT child of type `ChildComponent`
@ContentChild(ChildComponent) contentChild: ChildComponent;
ngAfterContentInit() {
// contentChild is set after the content has been initialized
this.logIt('AfterContentInit');
this.doSomething();
}
ngAfterContentChecked() {
// contentChild is updated after the content has been checked
if (this.prevHero === this.contentChild.hero) {
this.logIt('AfterContentChecked (no change)');
} else {
this.prevHero = this.contentChild.hero;
this.logIt('AfterContentChecked');
this.doSomething();
}
}
// ...
}
Без проблем с однонаправленным потоком для AfterContent
Метод doSomething() компонента обновляет свойство данных компонента comment сразу. Нет необходимости ожидать.
Напомним, что Angular вызывает обе связи AfterContent до вызова любой из связей AfterView. Angular завершает композицию спроецированного содержимого до завершения композиции представления этого компонента. Существует небольшой промежуток между связями AfterContent... и AfterView... для изменения представления хоста.
© 2010–2017 Google, Inc.
Licensed under the Creative Commons Attribution License 4.0.
https://v2.angular.io/docs/ts/latest/guide/lifecycle-hooks.html