填寫這份《一分鐘調查》,幫我們(開發組)做得更好!去填寫Home

生命週期鉤子

Lifecycle hooks

當 Angular 實例化元件類別並渲染元件檢視及其子檢視時,元件實例的生命週期就開始了。生命週期一直伴隨著變更檢測,Angular 會檢查資料繫結屬性何時發生變化,並按需更新檢視和元件實例。當 Angular 銷燬元件實例並從 DOM 中移除它渲染的範本時,生命週期就結束了。當 Angular 在執行過程中建立、更新和銷燬實例時,指令就有了類似的生命週期。

A component instance has a lifecycle that starts when Angular instantiates the component class and renders the component view along with its child views. The lifecycle continues with change detection, as Angular checks to see when data-bound properties change, and updates both the view and the component instance as needed. The lifecycle ends when Angular destroys the component instance and removes its rendered template from the DOM. Directives have a similar lifecycle, as Angular creates, updates, and destroys instances in the course of execution.

你的應用可以使用生命週期鉤子方法來觸發元件或指令生命週期中的關鍵事件,以初始化新實例,需要時啟動變更檢測,在變更檢測過程中響應更新,並在刪除實例之前進行清理。

Your application can use lifecycle hook methods to tap into key events in the lifecycle of a component or directive in order to initialize new instances, initiate change detection when needed, respond to updates during change detection, and clean up before deletion of instances.

先決條件

Prerequisites

在使用生命週期鉤子之前,你應該對這些內容有一個基本的瞭解:

Before working with lifecycle hooks, you should have a basic understanding of the following:

響應生命週期事件

Responding to lifecycle events

你可以透過實現一個或多個 Angular core 函式庫中定義的生命週期鉤子介面來響應元件或指令生命週期中的事件。這些鉤子讓你有機會在適當的時候對元件或指令實例進行操作,比如 Angular 建立、更新或銷燬這個實例時。

You can respond to events in the lifecycle of a component or directive by implementing one or more of the lifecycle hook interfaces in the Angular core library. The hooks give you the opportunity to act on a component or directive instance at the appropriate moment, as Angular creates, updates, or destroys that instance.

每個介面都有唯一的一個鉤子方法,它們的名字是由介面名再加上 ng 字首構成的。比如,OnInit 介面的鉤子方法叫做 ngOnInit()。如果你在元件或指令類別中實現了這個方法,Angular 就會在首次檢查完元件或指令的輸入屬性後,緊接著呼叫它。

Each interface defines the prototype for a single hook method, whose name is the interface name prefixed with ng. For example, the OnInit interface has a hook method named ngOnInit(). If you implement this method in your component or directive class, Angular calls it shortly after checking the input properties for that component or directive for the first time.

peek-a-boo.directive.ts (excerpt)
      
      @Directive()
export class PeekABooDirective implements OnInit {
  constructor(private logger: LoggerService) { }

  // implement OnInit's `ngOnInit` method
  ngOnInit() {
    this.logIt(`OnInit`);
  }

  logIt(msg: string) {
    this.logger.log(`#${nextId++} ${msg}`);
  }
}
    

你不必實現所有生命週期鉤子,只要實現你需要的那些就可以了。

You don't have to implement all (or any) of the lifecycle hooks, just the ones you need.

生命週期的順序

Lifecycle event sequence

當你的應用透過呼叫建構函式來實例化一個元件或指令時,Angular 就會呼叫那個在該實例生命週期的適當位置實現了的那些鉤子方法。

After your application instantiates a component or directive by calling its constructor, Angular calls the hook methods you have implemented at the appropriate point in the lifecycle of that instance.

Angular 會按以下順序執行鉤子方法。你可以用它來執行以下型別的操作。

Angular executes hook methods in the following sequence. You can use them to perform the following kinds of operations.

鉤子方法

Hook method

用途

Purpose

時機

Timing

ngOnChanges()

當 Angular 設定或重新設定資料繫結的輸入屬性時響應。 該方法接受當前和上一屬性值的 SimpleChanges 物件

Respond when Angular sets or resets data-bound input properties. The method receives a SimpleChanges object of current and previous property values.

注意,這發生的非常頻繁,所以你在這裡執行的任何操作都會顯著影響效能。 欲知詳情,參閱本文件的使用變更檢測鉤子

Note that this happens very frequently, so any operation you perform here impacts performance significantly. See details in Using change detection hooks in this document.

ngOnInit() 之前以及所繫結的一個或多個輸入屬性的值發生變化時都會呼叫。

Called before ngOnInit() and whenever one or more data-bound input properties change.

注意,如果你的元件沒有輸入,或者你使用它時沒有提供任何輸入,那麼框架就不會呼叫 ngOnChanges()

Note that if your component has no inputs or you use it without providing any inputs, the framework will not call ngOnChanges().

ngOnInit()

在 Angular 第一次顯示資料繫結和設定指令/元件的輸入屬性之後,初始化指令/元件。 欲知詳情,參閱本文件中的初始化元件或指令

Initialize the directive or component after Angular first displays the data-bound properties and sets the directive or component's input properties. See details in Initializing a component or directive in this document.

在第一輪 ngOnChanges() 完成之後呼叫,只調用一次

Called once, after the first ngOnChanges().

ngDoCheck()

檢測,並在發生 Angular 無法或不願意自己檢測的變化時作出反應。 欲知詳情和範例,參閱本文件中的自訂變更檢測

Detect and act upon changes that Angular can't or won't detect on its own. See details and example in Defining custom change detection in this document.

緊跟在每次執行變更檢測時的 ngOnChanges() 和 首次執行變更檢測時的 ngOnInit() 後呼叫。

Called immediately after ngOnChanges() on every change detection run, and immediately after ngOnInit() on the first run.

ngAfterContentInit()

當 Angular 把外部內容投影進元件檢視或指令所在的檢視之後呼叫。

Respond after Angular projects external content into the component's view, or into the view that a directive is in.

欲知詳情和範例,參閱本文件中的響應內容中的變更

See details and example in Responding to changes in content in this document.

第一次 ngDoCheck() 之後呼叫,只調用一次。

Called once after the first ngDoCheck().

ngAfterContentChecked()

每當 Angular 檢查完被投影到元件或指令中的內容之後呼叫。

Respond after Angular checks the content projected into the directive or component.

欲知詳情和範例,參閱本文件中的響應被投影內容的變更

See details and example in Responding to projected content changes in this document.

ngAfterContentInit() 和每次 ngDoCheck() 之後呼叫

Called after ngAfterContentInit() and every subsequent ngDoCheck().

ngAfterViewInit()

當 Angular 初始化完元件檢視及其子檢視或包含該指令的檢視之後呼叫。

Respond after Angular initializes the component's views and child views, or the view that contains the directive.

欲知詳情和範例,參閱本文件中的響應檢視變更

See details and example in Responding to view changes in this document.

第一次 ngAfterContentChecked() 之後呼叫,只調用一次。

Called once after the first ngAfterContentChecked().

ngAfterViewChecked()

每當 Angular 做完元件檢視和子檢視或包含該指令的檢視的變更檢測之後呼叫。

Respond after Angular checks the component's views and child views, or the view that contains the directive.

ngAfterViewInit() 和每次 ngAfterContentChecked() 之後呼叫。

Called after the ngAfterViewInit() and every subsequent ngAfterContentChecked().

ngOnDestroy()

每當 Angular 每次銷燬指令/元件之前呼叫並清掃。 在這兒反訂閱可觀察物件和分離事件處理器,以防記憶體洩漏。 欲知詳情,參閱本文件中的在實例銷燬時進行清理

Cleanup just before Angular destroys the directive or component. Unsubscribe Observables and detach event handlers to avoid memory leaks. See details in Cleaning up on instance destruction in this document.

在 Angular 銷燬指令或元件之前立即呼叫。

Called immediately before Angular destroys the directive or component.

生命週期範例

Lifecycle example set

現場演練 / 下載範例透過在受控於根元件 AppComponent 的一些元件上進行的一系列練習,示範了生命週期鉤子的運作方式。 每一個例子中,元件都扮演了元件測試臺的角色,以展示出一個或多個生命週期鉤子方法。

The現場演練 / 下載範例demonstrates the use of lifecycle hooks through a series of exercises presented as components under the control of the root AppComponent. In each case a parent component serves as a test rig for a child component that illustrates one or more of the lifecycle hook methods.

下表列出了這些練習及其簡介。 範例程式碼也用來闡明後續各節的一些特定任務。

The following table lists the exercises with brief descriptions. The sample code is also used to illustrate specific tasks in the following sections.

元件

Component

說明

Description

Peek-a-boo

展示每個生命週期鉤子,每個鉤子方法都會在螢幕上顯示一條日誌。

Demonstrates every lifecycle hook. Each hook method writes to the on-screen log.

Spy

展示了你如何在自訂指令中使用生命週期鉤子。 SpyDirective 實現了 ngOnInit()ngOnDestroy() 鉤子,並且使用它們來觀察和彙報一個元素何時進入或離開當前檢視。

Shows how you can use lifecycle hooks with a custom directive. The SpyDirective implements the ngOnInit() and ngOnDestroy() hooks, and uses them to watch and report when an element goes in or out of the current view.

OnChanges

示範了每當元件的輸入屬性之一發生變化時,Angular 如何呼叫 ngOnChanges() 鉤子。並且示範瞭如何解釋傳給鉤子方法的 changes 物件。

Demonstrates how Angular calls the ngOnChanges() hook every time one of the component input properties changes, and shows how to interpret the changes object passed to the hook method.

DoCheck

實現了一個 ngDoCheck() 方法,透過它可以自訂變更檢測邏輯。 監視該鉤子把哪些變更記錄到了日誌中,觀察 Angular 以什麼頻度呼叫這個鉤子。

Implements the ngDoCheck() method with custom change detection. Watch the hook post changes to a log to see how often Angular calls this hook.

AfterView

顯示 Angular 中的檢視所指的是什麼。 示範了 ngAfterViewInit()ngAfterViewChecked() 鉤子。

Shows what Angular means by a view. Demonstrates the ngAfterViewInit() and ngAfterViewChecked() hooks.

AfterContent

展示如何把外部內容投影進元件中,以及如何區分“投影進來的內容”和“元件的子檢視”。 示範了 ngAfterContentInit()ngAfterContentChecked() 鉤子。

Shows how to project external content into a component and how to distinguish projected content from a component's view children. Demonstrates the ngAfterContentInit() and ngAfterContentChecked() hooks.

計數器

Counter

示範了一個元件和一個指令的組合,它們各自有自己的鉤子。

Demonstrates a combination of a component and a directive, each with its own hooks.

初始化元件或指令

Initializing a component or directive

使用 ngOnInit() 方法執行以下初始化任務。

Use the ngOnInit() method to perform the following initialization tasks.

  • 在建構函式外部執行復雜的初始化。元件的構造應該既便宜又安全。比如,你不應該在元件建構函式中獲取資料。當在測試中建立元件時或者決定顯示它之前,你不應該擔心新元件會嘗試聯絡遠端伺服器。

    Perform complex initializations outside of the constructor. Components should be cheap and safe to construct. You should not, for example, fetch data in a component constructor. You shouldn't worry that a new component will try to contact a remote server when created under test or before you decide to display it.

    ngOnInit() 是元件獲取初始資料的好地方。比如,英雄之旅課程

    An ngOnInit() is a good place for a component to fetch its initial data. For an example, see the Tour of Heroes tutorial.

  • 在 Angular 設定好輸入屬性之後設定元件。建構函式應該只把初始區域性變數設定為簡單的值。

    Set up the component after Angular sets the input properties. Constructors should do no more than set the initial local variables to simple values.

    請記住,只有在構造完成之後才會設定指令的資料繫結輸入屬性。如果要根據這些屬性對指令進行初始化,請在執行 ngOnInit() 時設定它們。

    Keep in mind that a directive's data-bound input properties are not set until after construction. If you need to initialize the directive based on those properties, set them when ngOnInit() runs.

    ngOnChanges() 方法是你能訪問這些屬性的第一次機會。Angular 會在呼叫 ngOnInit() 之前呼叫 ngOnChanges(),而且之後還會呼叫多次。但它只調用一次 ngOnInit()

    The ngOnChanges() method is your first opportunity to access those properties. Angular calls ngOnChanges() before ngOnInit(), but also many times after that. It only calls ngOnInit() once.

在實例銷燬時進行清理

Cleaning up on instance destruction

把清理邏輯放進 ngOnDestroy() 中,這個邏輯就必然會在 Angular 銷燬該指令之前執行。

Put cleanup logic in ngOnDestroy(), the logic that must run before Angular destroys the directive.

這裡是釋放資源的地方,這些資源不會自動被垃圾回收。如果你不這樣做,就存在記憶體洩漏的風險。

This is the place to free resources that won't be garbage-collected automatically. You risk memory leaks if you neglect to do so.

  • 取消訂閱可觀察物件和 DOM 事件。

    Unsubscribe from Observables and DOM events.

  • 停止 interval 計時器。

    Stop interval timers.

  • 反註冊該指令在全域性或應用服務中註冊過的所有回呼(Callback)。

    Unregister all callbacks that the directive registered with global or application services.

ngOnDestroy() 方法也可以用來通知應用程式的其它部分,該元件即將消失。

The ngOnDestroy() method is also the time to notify another part of the application that the component is going away.

一般性例子

General examples

下面的例子展示了各個生命週期事件的呼叫順序和相對頻率,以及如何在元件和指令中單獨使用或同時使用這些鉤子。

The following examples demonstrate the call sequence and relative frequency of the various lifecycle events, and how the hooks can be used separately or together for components and directives.

所有生命週期事件的順序和頻率

Sequence and frequency of all lifecycle events

為了展示 Angular 如何以預期的順序呼叫鉤子,PeekABooComponent 示範了一個元件中的所有鉤子。

To show how Angular calls the hooks in the expected order, the PeekABooComponent demonstrates all of the hooks in one component.

實際上,你很少會(幾乎永遠不會)像這個示範中一樣實現所有這些介面。

In practice you would rarely, if ever, implement all of the interfaces the way this demo does.

下列快照反映了使用者單擊 Create... 按鈕,然後單擊 Destroy... 按鈕後的日誌狀態。

The following snapshot reflects the state of the log after the user clicked the Create... button and then the Destroy... button.

日誌資訊的日誌和所規定的鉤子呼叫順序是一致的: OnChangesOnInitDoCheck (3x)、AfterContentInitAfterContentChecked (3x)、 AfterViewInitAfterViewChecked (3x)和 OnDestroy

The sequence of log messages follows the prescribed hook calling order: OnChanges, OnInit, DoCheck (3x), AfterContentInit, AfterContentChecked (3x), AfterViewInit, AfterViewChecked (3x), and OnDestroy.

注意,該日誌確認了在建立期間那些輸入屬性(這裡是 name 屬性)沒有被賦值。 這些輸入屬性要等到 onInit() 中才可用,以便做進一步的初始化。

Notice that the log confirms that input properties (the name property in this case) have no assigned values at construction. The input properties are available to the onInit() method for further initialization.

如果使用者點選Update Hero按鈕,就會看到另一個 OnChanges 和至少兩組 DoCheckAfterContentCheckedAfterViewChecked 鉤子。 注意,這三種鉤子被觸發了很多次,所以讓它們的邏輯儘可能保持精簡是非常重要的!

Had the user clicked the Update Hero button, the log would show another OnChanges and two more triplets of DoCheck, AfterContentChecked and AfterViewChecked. Notice that these three hooks fire often, so it is important to keep their logic as lean as possible.

使用指令來監視 DOM

Use directives to watch the DOM

這個 Spy 例子示範瞭如何在指令和元件中使用鉤子方法。SpyDirective 實現了兩個鉤子 ngOnInit()ngOnDestroy(),以便發現被監視的元素什麼時候位於當前檢視中。

The Spy example demonstrates how you can use hook method for directives as well as components. The SpyDirective implements two hooks, ngOnInit() and ngOnDestroy(), in order to discover when a watched element is in the current view.

這個範本將 SpyDirective 應用到由父元件 SpyComponent 管理的 ngFor 內的 <div> 中。

This template applies the SpyDirective to a <div> in the ngFor hero repeater managed by the parent SpyComponent.

該例子不執行任何初始化或清理工作。它只是透過記錄指令本身的實例化時間和銷燬時間來追蹤元素在檢視中的出現和消失。

The example does not perform any initialization or clean-up. It just tracks the appearance and disappearance of an element in the view by recording when the directive itself is instantiated and destroyed.

像這樣的間諜指令可以深入瞭解你無法直接修改的 DOM 物件。你無法觸及原生 <div> 的實現,也無法修改第三方元件,但是可以用指令來監視這些元素。

A spy directive like this can provide insight into a DOM object that you cannot change directly. You can't touch the implementation of a native <div>, or modify a third party component. You can, however watch these elements with a directive.

這個指令定義了 ngOnInit()ngOnDestroy() 鉤子,它透過一個注入進來的 LoggerService 把訊息記錄到父元件中去。

The directive defines ngOnInit() and ngOnDestroy() hooks that log messages to the parent via an injected LoggerService.

src/app/spy.directive.ts
      
      let nextId = 1;

// Spy on any element to which it is applied.
// Usage: <div appSpy>...</div>
@Directive({selector: '[appSpy]'})
export class SpyDirective implements OnInit, OnDestroy {
  private id = nextId++;

  constructor(private logger: LoggerService) { }

  ngOnInit() {
    this.logger.log(`Spy #${this.id} onInit`);
  }

  ngOnDestroy() {
    this.logger.log(`Spy #${this.id} onDestroy`);
  }
}
    

你可以把這個偵探指令寫到任何原生元素或元件元素上,以觀察它何時被初始化和銷燬。 下面是把它附加到用來重複顯示英雄資料的這個 <div> 上。

You can apply the spy to any native or component element, and see that it is initialized and destroyed at the same time as that element. Here it is attached to the repeated hero <div>:

src/app/spy.component.html
      
      <p *ngFor="let hero of heroes" appSpy>
  {{hero}}
</p>
    

每個“偵探”的建立和銷燬都可以標出英雄所在的那個 <div> 的出現和消失。鉤子記錄中的結構是這樣的:

Each spy's creation and destruction marks the appearance and disappearance of the attached hero <div> with an entry in the Hook Log as seen here:

新增一個英雄就會產生一個新的英雄 <div>。偵探的 ngOnInit() 記錄下了這個事件。

Adding a hero results in a new hero <div>. The spy's ngOnInit() logs that event.

Reset 按鈕清除了這個 heroes 列表。 Angular 從 DOM 中移除了所有英雄的 div,並且同時銷燬了附加在這些 div 上的偵探指令。 偵探的 ngOnDestroy() 方法彙報了它自己的臨終時刻。

The Reset button clears the heroes list. Angular removes all hero <div> elements from the DOM and destroys their spy directives at the same time. The spy's ngOnDestroy() method reports its last moments.

同時使用元件和指令的鉤子

Use component and directive hooks together

在這個例子中,CounterComponent 使用了 ngOnChanges() 方法,以便在每次父元件遞增其輸入屬性 counter 時記錄一次變更。

In this example, a CounterComponent uses the ngOnChanges() method to log a change every time the parent component increments its input counter property.

這個例子將前例中的 SpyDirective 用於 CounterComponent 的日誌,以便監視這些日誌條目的建立和銷燬。

This example applies the SpyDirective from the previous example to the CounterComponent log, in order to watch the creation and destruction of log entries.

使用變更檢測鉤子

Using change detection hooks

一旦檢測到該元件或指令的輸入屬性發生了變化,Angular 就會呼叫它的 ngOnChanges() 方法。 這個 onChanges 範例透過監控 OnChanges() 鉤子示範了這一點。

Angular calls the ngOnChanges() method of a component or directive whenever it detects changes to the input properties. The onChanges example demonstrates this by monitoring the OnChanges() hook.

on-changes.component.ts (excerpt)
      
      ngOnChanges(changes: SimpleChanges) {
  for (const propName in changes) {
    const chng = changes[propName];
    const cur  = JSON.stringify(chng.currentValue);
    const prev = JSON.stringify(chng.previousValue);
    this.changeLog.push(`${propName}: currentValue = ${cur}, previousValue = ${prev}`);
  }
}
    

ngOnChanges() 方法獲取了一個物件,它把每個發生變化的屬性名都對映到了一個SimpleChange物件, 該物件中有屬性的當前值和前一個值。這個鉤子會在這些發生了變化的屬性上進行迭代,並記錄它們。

The ngOnChanges() method takes an object that maps each changed property name to a SimpleChange object holding the current and previous property values. This hook iterates over the changed properties and logs them.

這個例子中的 OnChangesComponent 元件有兩個輸入屬性:heropower

The example component, OnChangesComponent, has two input properties: hero and power.

src/app/on-changes.component.ts
      
      @Input() hero!: Hero;
@Input() power = '';
    

宿主 OnChangesParentComponent 綁定了它們,就像這樣:

The host OnChangesParentComponent binds to them as follows.

src/app/on-changes-parent.component.html
      
      <on-changes [hero]="hero" [power]="power"></on-changes>
    

下面是此例子中的當使用者做出更改時的操作示範:

Here's the sample in action as the user makes changes.

日誌條目把 power 屬性的變化顯示為字串。但請注意,ngOnChanges() 方法不會捕獲對 hero.name 更改。這是因為只有當輸入屬性的值發生變化時,Angular 才會呼叫該鉤子。在這種情況下,hero 是輸入屬性,hero 屬性的值是對 hero 物件參考 。當它自己的 name 屬性的值發生變化時,物件參考並沒有改變。

The log entries appear as the string value of the power property changes. Notice, however, that the ngOnChanges() method does not catch changes to hero.name. This is because Angular calls the hook only when the value of the input property changes. In this case, hero is the input property, and the value of the hero property is the reference to the hero object. The object reference did not change when the value of its own name property changed.

響應檢視的變更

Responding to view changes

當 Angular 在變更檢測期間遍歷檢視樹時,需要確保子元件中的某個變更不會嘗試更改其父元件中的屬性。因為單向資料流的工作原理就是這樣的,這樣的更改將無法正常渲染。

As Angular traverses the view hierarchy during change detection, it needs to be sure that a change in a child does not attempt to cause a change in its own parent. Such a change would not be rendered properly, because of how unidirectional data flow works.

如果你需要做一個與預期資料流反方向的修改,就必須觸發一個新的變更檢測週期,以允許渲染這種變更。這些例子說明了如何安全地做出這些改變。

If you need to make a change that inverts the expected data flow, you must trigger a new change detection cycle to allow that change to be rendered. The examples illustrate how to make such changes safely.

AfterView 例子展示了 AfterViewInit()AfterViewChecked() 鉤子,Angular 會在每次建立了元件的子檢視後呼叫它們。

The AfterView sample explores the AfterViewInit() and AfterViewChecked() hooks that Angular calls after it creates a component's child views.

下面是一個子檢視,它用來把英雄的名字顯示在一個 <input> 中:

Here's a child view that displays a hero's name in an <input>:

ChildViewComponent
      
      @Component({
  selector: 'app-child-view',
  template: `<label for="hero-name">Hero name: </label>
  <input type="text" id="hero-name" [(ngModel)]="hero">`
})
export class ChildViewComponent {
  hero = 'Magneta';
}
    

AfterViewComponent 把這個子檢視顯示在它的範本中

The AfterViewComponent displays this child view within its template:

AfterViewComponent (template)
      
      template: `
  <div>child view begins</div>
    <app-child-view></app-child-view>
  <div>child view ends</div>`
    

下列鉤子基於子檢視中的每一次資料變更採取行動,它只能透過帶@ViewChild裝飾器的屬性來訪問子檢視。

The following hooks take action based on changing values within the child view, which can only be reached by querying for the child view via the property decorated with @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();
    }
  }
  // ...
}
    

在更新檢視之前等待

Wait before updating the view

在這個例子中,當英雄名字超過 10 個字元時,doSomething() 方法會更新螢幕,但在更新 comment 之前會等一個節拍(tick)。

In this example, the doSomething() method updates the screen when the hero name exceeds 10 characters, but waits a tick before updating comment.

AfterViewComponent (doSomething)
      
      // This surrogate for real business logic sets the `comment`
private doSomething() {
  const 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);
  }
}
    

在元件的檢視合成完之後,就會觸發 AfterViewInit()AfterViewChecked() 鉤子。如果你修改了這段程式碼,讓這個鉤子立即修改該元件的資料繫結屬性 comment,你就會發現 Angular 丟擲一個錯誤。

Both the AfterViewInit() and AfterViewChecked() hooks fire after the component's view has been composed. If you modify the code so that the hook updates the component's data-bound comment property immediately, you can see that Angular throws an error.

LoggerService.tick_then() 語句把日誌的更新工作推遲了一個瀏覽器 JavaScript 週期,也就觸發了一個新的變更檢測週期。

The LoggerService.tick_then() statement postpones the log update for one turn of the browser's JavaScript cycle, which triggers a new change-detection cycle.

編寫精簡的鉤子方法來避免效能問題

Write lean hook methods to avoid performance problems

當你執行 AfterView 範例時,請注意當沒有發生任何需要注意的變化時,Angular 仍然會頻繁的呼叫 AfterViewChecked()。 要非常小心你放到這些方法中的邏輯或計算量。

When you run the AfterView sample, notice how frequently Angular calls AfterViewChecked()-often when there are no changes of interest. Be very careful about how much logic or computation you put into one of these methods.

響應被投影內容的變更

Responding to projected content changes

內容投影是從元件外部匯入 HTML 內容,並把它插入在元件範本中指定位置上的一種途徑。 你可以在目標中透過查詢下列結構來認出內容投影。

Content projection is a way to import HTML content from outside the component and insert that content into the component's template in a designated spot. You can identify content projection in a template by looking for the following constructs.

  • 元素標籤中間的 HTML。

    HTML between component element tags.

  • 元件範本中的 <ng-content> 標籤。

    The presence of <ng-content> tags in the component's template.

AngularJS 的開發者把這種技術叫做 transclusion

AngularJS developers know this technique as transclusion.

這個 AfterContent 例子探索了 AfterContentInit()AfterContentChecked() 鉤子。Angular 會在把外部內容投影進該元件時呼叫它們。

The AfterContent sample explores the AfterContentInit() and AfterContentChecked() hooks that Angular calls after Angular projects external content into the component.

對比前面的 AfterView 例子考慮這個變化。 這次不再透過範本來把子檢視包含進來,而是改為從 AfterContentComponent 的父元件中匯入它。下面是父元件的範本:

Consider this variation on the previous AfterView example. This time, instead of including the child view within the template, it imports the content from the AfterContentComponent's parent. The following is the parent's template.

AfterContentParentComponent (template excerpt)
      
      `<after-content>
  <app-child></app-child>
</after-content>`
    

注意,<app-child> 標籤被包含在 <after-content> 標籤中。 永遠不要在元件標籤的內部放任何內容 —— 除非你想把這些內容投影進這個元件中

Notice that the <app-child> tag is tucked between the <after-content> tags. Never put content between a component's element tags unless you intend to project that content into the component.

現在來看該元件的範本:

Now look at the component's template.

AfterContentComponent (template)
      
      template: `
  <div>projected content begins</div>
    <ng-content></ng-content>
  <div>projected content ends</div>`
    

<ng-content> 標籤是外來內容的佔位符。 它告訴 Angular 在哪裡插入這些外來內容。 在這裡,被投影進去的內容就是來自父元件的 <app-child> 標籤。

The <ng-content> tag is a placeholder for the external content. It tells Angular where to insert that content. In this case, the projected content is the <app-child> from the parent.

使用 AfterContent 鉤子

Using AfterContent hooks

AfterContent 鉤子和 AfterView 相似。關鍵的不同點是子元件的型別不同。

AfterContent hooks are similar to the AfterView hooks. The key difference is in the child component.

  • AfterView 鉤子所關心的是 ViewChildren,這些子元件的元素標籤會出現在該元件的範本裡面

    The AfterView hooks concern ViewChildren, the child components whose element tags appear within the component's template.

  • AfterContent 鉤子所關心的是 ContentChildren,這些子元件被 Angular 投影進該元件中。

    The AfterContent hooks concern ContentChildren, the child components that Angular projected into the component.

下列 AfterContent 鉤子基於子級內容中值的變化而採取相應的行動,它只能透過帶有@ContentChild裝飾器的屬性來查詢到“子級內容”。

The following AfterContent hooks take action based on changing values in a content child, which can only be reached by querying for them via the property decorated with @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();
    }
  }
  // ...
}
    
不需要等待內容更新
No need to wait for content updates

該元件的 doSomething() 方法會立即更新該元件的資料繫結屬性 comment。而無需延遲更新以確保正確渲染

This component's doSomething() method updates the component's data-bound comment property immediately. There's no need to delay the update to ensure proper rendering.

Angular 在呼叫 AfterView 鉤子之前,就已呼叫完所有的 AfterContent 鉤子。 在完成該元件檢視的合成之前, Angular 就已經完成了所投影內容的合成工作。 AfterContent...AfterView... 鉤子之間有一個小的時間窗,允許你修改宿主檢視。

Angular calls both AfterContent hooks before calling either of the AfterView hooks. Angular completes composition of the projected content before finishing the composition of this component's view. There is a small window between the AfterContent... and AfterView... hooks that allows you to modify the host view.

自訂變更檢測邏輯

Defining custom change detection

要監控 ngOnChanges() 無法捕獲的變更,你可以實現自己的變更檢查邏輯,比如 DoCheck 的例子。這個例子展示了你如何使用 ngDoCheck() 鉤子來檢測和處理 Angular 自己沒有捕捉到的變化。

To monitor changes that occur where ngOnChanges() won't catch them, you can implement your own change check, as shown in the DoCheck example. This example shows how you can use the ngDoCheck() hook to detect and act upon changes that Angular doesn't catch on its own.

DoCheck 範例使用下面的 ngDoCheck() 鉤子擴充套件了 OnChanges 範例:

The DoCheck sample extends the OnChanges sample with the following ngDoCheck() hook:

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.
      const count = this.noChangeCount += 1;
      const 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;
}
    

這段程式碼會檢查某些感興趣的值,捕獲並把它們當前的狀態和之前的進行比較。當 heropower 沒有實質性變化時,它就會在日誌中寫一條特殊的資訊,這樣你就能看到 DoCheck() 被呼叫的頻率。其結果很有啟發性。

This code inspects certain values of interest, capturing and comparing their current state against previous values. It writes a special message to the log when there are no substantive changes to the hero or the power so you can see how often DoCheck() is called. The results are illuminating.

雖然 ngDoCheck() 鉤子可以檢測出英雄的 name 何時發生了變化,但卻非常昂貴。無論變化發生在何處,每個變化檢測週期都會以很大的頻率呼叫這個鉤子。在使用者可以執行任何操作之前,本例中已經呼叫了20多次。

While the ngDoCheck() hook can detect when the hero's name has changed, it is very expensive. This hook is called with enormous frequency—after every change detection cycle no matter where the change occurred. It's called over twenty times in this example before the user can do anything.

這些初始化檢查大部分都是由 Angular 首次在頁面的其它地方渲染不相關的資料觸發的。只要把游標移動到另一個 <input> 就會觸發一次呼叫。其中的少數呼叫揭示了相關資料的實際變化情況。如果使用這個鉤子,那麼你的實現必須非常輕量級,否則會損害使用者體驗。

Most of these initial checks are triggered by Angular's first rendering of unrelated data elsewhere on the page. Just moving the cursor into another <input> triggers a call. Relatively few calls reveal actual changes to pertinent data. If you use this hook, your implementation must be extremely lightweight or the user experience suffers.