AngularDart 4.0 高級-生命週期鉤子

組件有一個由Angular本身管理的生命週期。html

Angular建立它,渲染它,建立和渲染它的子項,在數據綁定屬性發生變化時對其進行檢查,並在將它從DOM中刪除以前對其進行銷燬。java

Angular提供生命週期掛鉤,提供這些關鍵生命時刻的可視性以及發生時的行爲能力。web

指令具備相同的生命週期掛鉤集,減去特定於組件內容和視圖的掛鉤。api

組件生命週期掛鉤

指令和組件實例的生命週期與Angular建立,更新和摧毀它們同樣。 開發人員能夠經過在Angular core庫中實現一個或多個Lifecycle Hook界面來挖掘該生命週期中的關鍵時刻。瀏覽器

每一個接口都有一個單一的鉤子方法,其名稱是以ng開頭的接口名稱。 例如,OnInit接口有一個名爲ngOnInit的鉤子方法,Angular在建立組件後當即調用:安全

lib/src/peek_a_boo_component.dart (ngOnInit)服務器

class PeekABoo implements OnInit {
  final LoggerService _logger;

  PeekABoo(this._logger);

  // implement OnInit's `ngOnInit` method
  void ngOnInit() {
    _logIt('OnInit');
  }

  void _logIt(String msg) {
    // Don't tick or else
    // the AfterContentChecked and AfterViewChecked recurse.
    // Let parent call tick()
    _logger.log("#${_nextId++} $msg");
  }
}

沒有指令或組件會實現全部的生命週期鉤子,而且一些鉤子只對組件有意義。 若是它被定義了,Angular只會調用一個指令/組件鉤子方法。app

生命週期序列

經過調用其構造函數建立組件/指令後,Angular在特定時刻按如下順序調用生命週期鉤子方法:ide

鉤子 做用和時機
ngOnChanges

Angular(從新)設置數據綁定輸入屬性時響應。 該方法接收當前和前一個屬性值的SimpleChanges對象。函數

ngOnInit以前調用而且每當有一個或多個數據綁定輸入屬性發生變化時調用。

ngOnInit

在Angular首次顯示數據綁定屬性並設置指令/組件的輸入屬性後,初始化指令/組件。

在第一次ngOnChanges以後調用一次。

ngDoCheck

檢測Angular沒法或沒法自行檢測到的更改並採起相應措施。

在每次更改檢測運行期間,當即在ngOnChangesngOnInit以後調用。

ngAfterContentInit

在Angular將外部內容投影到組件的視圖以後進行響應。

在第一次NgDoCheck以後調用一次。

組件獨有的鉤子。

ngAfterContentChecked

在Angular檢查投影到組件中的內容以後做出響應。

ngAfterContentInit和後續的每次NgDoCheck以後調用。

組件獨有的鉤子。

ngAfterViewInit

在Angular初始化組件的視圖和子視圖以後進行響應,。

在第一次ngAfterContentChecked以後調用一次。

組件獨有的鉤子。

ngAfterViewChecked

在Angular檢查組件的視圖和子視圖以後做出響應。

ngAfterViewInit和後續的每次ngAfterContentChecked以後調用。

組件獨有的鉤子。

ngOnDestroy

在Angular摧毀指令/組件以前進行清理。 取消訂閱observables並分離事件處理程序以免內存泄漏。

在Angular摧毀指令/組件以前調用。

其餘生命週期掛鉤

其餘Angular子系統除了這些組件鉤子可能有本身的生命週期鉤子。

例如,路由器也有本身的路由器生命週期掛鉤,可讓咱們利用路由導航中的特定時刻。 能夠在ngOnInitrouterOnActivate之間繪製一個平行線。 二者的前綴都是爲了不碰撞,而且在組件初始化時都運行正確。

第三方庫可能也會實現它們的鉤子,以便讓開發人員更好地控制這些庫的使用方式。

生命週期練習

經過組件的一系列練習在根AppComponent的控制下呈現來演示生命週期掛鉤。

它們遵循一種常見的模式:父組件做爲一個子組件的一個或多個生命週期鉤子方法的測試裝備。

如下是每一個練習的簡要說明:

組件 描述
Peek-a-boo 演示每一個生命週期的鉤子。 每一個掛鉤方法都會寫入屏幕日誌。
Spy

指令也有生命週期掛鉤。 SpyDirective可使用ngOnInitngOnDestroy掛鉤建立或銷燬它探測的元素。

此示例將SpyDirective應用於由父SpyComponent管理的ngFor英雄迭代器中的<div>

OnChanges 看看每次組件輸入屬性發生變化時,Angular如何用變動對象調用ngOnChanges鉤子。 顯示如何解釋更改對象。
DoCheck 使用自定義更改檢測實現ngDoCheck方法。 看看Angular多久會調用這個鉤子,並在更改日誌後觀察它。
AfterView 經過視圖顯示Angular的意圖。 演示ngAfterViewInit和ngAfterViewChecked掛鉤。
AfterContent 演示如何將外部內容投影到組件中,以及如何區分組件的視圖中的投影內容和子組件。 演示ngAfterContentInitngAfterContentChecked掛鉤。
Counter

演示組件和指令的組合,每一個組件都有本身的鉤子。

在此示例中,每次父組件遞增其輸入計數器屬性時,CounterComponent都會記錄更改(經過ngOnChanges)。 同時,前面例子中的SpyDirective被應用到CounterComponent日誌中,它監視正在建立和銷燬的日誌條目。

本章的其他部分將進一步詳細討論選定的練習

Peek-a-boo:全部鉤子

PeekABooComponent演示了一個組件中的全部鉤子。

若是有的話,你不多會實現像這樣的全部接口。 peek-a-boo存在以顯示Angular如何按預期順序調用鉤子。

此快照反映用戶單擊「建立...」按鈕而後單擊「銷燬...」按鈕後日志的狀態。

日誌消息的順序遵循規定的鉤子調用順序:OnChangesOnInitDoCheck(3x),AfterContentInitAfterContentChecked(3x),AfterViewInitAfterViewChecked(3x)和OnDestroy

構造函數自己不是一個Angular鉤子。 日誌確認輸入屬性(在這種狀況下的name屬性)在構造時沒有分配的值。

若是用戶點擊Update Hero按鈕,日誌會顯示另外一個OnChanges和兩個更多的DoCheck,AfterContentChecked和AfterViewChecked三元組。 顯然這三個鉤子常常發射。 儘量保持這些鉤子中的邏輯!

接下來的例子集中於鉤子細節。

刺探OnInit和OnDestroy

使用這兩個間諜鉤進行臥底探索,以發現元素什麼時候被初始化或銷燬。

這是指令的完美滲透工做。 英雄們永遠不會知道他們正在被監視。

一邊開玩笑,注意兩點:

  • Angular爲指令和組件調用鉤子方法。
  • 間諜指令能夠提供對不能直接更改的DOM對象的洞察。 顯然,你不能觸摸本地div的實現。 您也不能修改第三方組件。 可是你能夠監察一個指令。

這個偷偷摸摸的間諜指令很簡單,幾乎徹底由ngOnInitngOnDestroy鉤子組成,這些鉤子經過注入的LoggerService將消息記錄到父級。

// Spy on any element to which it is applied.
// Usage: <div mySpy>...</div>
@Directive(selector: '[mySpy]')
class SpyDirective implements OnInit, OnDestroy {
  final LoggerService _logger;

  SpyDirective(this._logger);

  ngOnInit() => _logIt('onInit');

  ngOnDestroy() => _logIt('onDestroy');

  _logIt(String msg) => _logger.log('Spy #${_nextId++} $msg');
}

您能夠將間諜應用到任何本機或組件元素,而且會與該元素的同一時間進行初始化和銷燬。 在這裏它被附加到重複的英雄<div>

<div *ngFor="let hero of heroes" mySpy class="heroes">
  {{hero}}
</div>

每一個間諜的出生和死亡標誌着所附英雄<div>的出生和死亡,並在Hook Log中有一個條目,以下所示:

添加一個英雄會產生一個新的英雄<div>。 間諜的ngOnInit記錄該事件。

重置按鈕清除英雄列表。 Angular從DOM中移除全部英雄<div>元素並同時銷燬他們的間諜指令。 間諜的ngOnDestroy方法報告其最後時刻。

ngOnInitngOnDestroy方法在實際應用中扮演更重要的角色。

OnInit

使用ngOnInit有兩個主要緣由:

  • 在施工後不久執行復雜的初始化
  • 在Angular設置輸入屬性後設置組件

有經驗的開發人員贊成組件應該便於構建且安全。

Angular團隊負責人Misko Hevery解釋了爲何您應該避免使用複雜的構造函數邏輯。

不要在組件構造函數中獲取數據。您不該該擔憂當在測試下建立或決定顯示以前時新組件會嘗試聯繫遠程服務器。構造函數不該僅僅將初始局部變量設置爲簡單值。

ngOnInit是組件獲取其初始數據的好地方。 教程HTTP章節顯示瞭如何。

還要記住,指令的數據綁定輸入屬性在構建以後纔會設置。 若是您須要根據這些屬性初始化指令,那麼這是一個問題。 當ngOninit運行時,它們將被設置。

ngOnChanges方法是您第一次訪問這些屬性的機會。 在ngOnInit以前Angular會調用ngOnChanges ...並在此以後屢次調用。 它只調用一次ngOnInit

您能夠期待Angular在建立組件後當即調用ngOnInit方法。 這就是深度初始化邏輯所屬的地方。

OnDestroy

將清理邏輯放入ngOnDestroy中,在Angular銷燬指令以前必須運行的邏輯。

這是通知應用程序的另外一部分組件將要銷燬的時間。

這是釋放資源的地方,不會自動收集垃圾。 取消訂閱observables和DOM事件。 中止間隔定時器。 取消註冊此指令在全局或應用服務中註冊的全部回調。 若是你忽視這樣作,你會冒內存泄漏的風險。

OnChanges

只要檢測到組件(或指令)的輸入屬性發生變化,Angular就會調用它的ngOnChanges方法。 這個例子監視OnChanges鉤子。

lib/src/on_changes_component.dart (ngOnChanges)

ngOnChanges(Map<String, SimpleChange> changes) {
  changes.forEach((String propName, SimpleChange change) {
    String cur = JSON.encode(change.currentValue);
    String prev = change.previousValue == null
        ? "{}"
        : JSON.encode(change.previousValue);
    changeLog.add('$propName: currentValue = $cur, previousValue = $prev');
  });
}

ngOnChanges方法接受一個對象,該對象將每一個已更改的屬性名稱映射到保存當前和前一個屬性值的SimpleChange對象。 這個鉤子迭代已更改的屬性並記錄它們。

示例組件OnChangesComponent具備兩個輸入屬性:heropower

@Input()
Hero hero;
@Input()
String power;

宿主OnChangesParentComponent像這樣綁定到它們:

<on-changes [hero]="hero" [power]="power"></on-changes>

如下是用戶進行更改時的示例。

日誌條目顯示爲power屬性更改的字符串值。 但ngOnChanges並無捕捉到hero.name的變化,這一開始使人驚訝。

當輸入屬性的值改變時,Angular只會調用鉤子。 hero屬性的值是對hero對象的引用。 Angular並不在乎英雄本身的name屬性發生了變化。 英雄對象引用沒有改變,因此從Angular的角度來看,沒有改變的反饋!

DoCheck

使用DoCheck鉤子來檢測並處理Angular本身沒法捕獲的更改。

使用此方法檢測Angular忽略的更改。

DoCheck示例使用如下ngDoCheck鉤子擴展了OnChanges示例:

lib/src/do_check_component.dart (ngDoCheck)

ngDoCheck() {
  if (hero.name != oldHeroName) {
    changeDetected = true;
    changeLog.add(
        'DoCheck: Hero name changed to "${hero.name}" from "$oldHeroName"');
    oldHeroName = hero.name;
  }

  if (power != oldPower) {
    changeDetected = true;
    changeLog.add('DoCheck: Power changed to "$power" from "$oldPower"');
    oldPower = power;
  }

  if (changeDetected) {
    noChangeCount = 0;
  } else {
    // log that hook was called when there was no relevant change.
    var count = noChangeCount += 1;
    var noChangeMsg =
        'DoCheck called ${count}x when no change to hero or power';
    if (count == 1) {
      // add new "no change" message
      changeLog.add(noChangeMsg);
    } else {
      // update last "no change" message
      changeLog[changeLog.length - 1] = noChangeMsg;
    }
  }

  changeDetected = false;
}

此代碼檢查某些感興趣的值,捕獲並比較其當前狀態與之前的值。 當英雄或權力沒有實質性變化時,它會向日志中寫入特殊消息,以便您能夠看到DoCheck被屢次調用。 結果是高亮的:

雖然ngDoCheck掛鉤能夠檢測到英雄的name什麼時候發生變化,但它的成本很是可怕。 這個鉤子以巨大的頻率被調用 - 在每一個變化檢測週期以後,不管變化發生在何處。 在用戶能夠作任何事情以前,在這個例子中它被調用了二十次。

大部分初始檢查都是由Angular在頁面其餘地方首次渲染(與數據無關)而觸發的。 僅僅經過鼠標移動到另外一個輸入框就會觸發一個呼叫。 相對較少的調用顯示相關數據的實際變化。 很顯然,咱們的實施必須很是輕便,不然用戶體驗將受到影響。

AfterView

AfterView樣本探討了Angular在建立組件的子視圖後調用的AfterViewInitAfterViewChecked掛鉤。

如下是在輸入框中顯示英雄名字的子視圖:

lib/src/after_view_component.dart (child view)

@Component(
  selector: 'my-child-view',
  template: '<input [(ngModel)]="hero">',
  directives: const [CORE_DIRECTIVES, formDirectives],
)
class ChildViewComponent {
  String hero = 'Magneta';
}

AfterViewComponent在其模板中顯示此子視圖:

lib/src/after_view_component.dart (template)

template: '''
  <div>-- child view begins --</div>
    <my-child-view></my-child-view>
  <div>-- child view ends --</div>
  <p *ngIf="comment.isNotEmpty" class="comment">{{comment}}</p>''',

如下鉤子根據更改子視圖內的值來執行操做,只能經過使用@ViewChild註解的屬性查詢子視圖來實現。

lib/src/after_view_component.dart (class excerpts)

class AfterViewComponent implements AfterViewChecked, AfterViewInit {
  var _prevHero = '';

  // Query for a VIEW child of type `ChildViewComponent`
  @ViewChild(ChildViewComponent)
  ChildViewComponent viewChild;

  ngAfterViewInit() {
    // viewChild is set after the view has been initialized
    _logIt('AfterViewInit');
    _doSomething();
  }

  ngAfterViewChecked() {
    // viewChild is updated after the view has been checked
    if (_prevHero == viewChild.hero) {
      _logIt('AfterViewChecked (no change)');
    } else {
      _prevHero = viewChild.hero;
      _logIt('AfterViewChecked');
      _doSomething();
    }
  }
  // ...
}

遵照單向數據流規則

當英雄名字超過10個字符時,doSomething方法更新屏幕。

lib/src/after_view_component.dart (doSomething)

// This surrogate for real business logic sets the `comment`
void _doSomething() {
  var c = viewChild.hero.length > 10 ? "That's a long name" : '';
  if (c != comment) {
    // Wait a tick because the component's view has already been checked
    _logger.tick().then((_) {
      comment = c;
    });
  }
}

爲何doSomething方法在更新comment以前等待一個tick

Angular的單向數據流規則禁止在視圖組成以後更新視圖。 組件視圖組合完成後,這兩個鉤子都會觸發。

若是鉤子當即更新組件的數據綁定comment屬性,Angular會拋出一個錯誤(嘗試它!)。

LoggerService.tick()推遲了瀏覽器更新週期的一第二天志更新......而且這足夠長。

這裏是AfterView的行動

請注意,常常在沒有感興趣的變化時,Angular常常調用AfterViewChecked。 編寫瘦鉤方法以免性能問題。

AfterContent

AfterContent示例探索在Angular將外部內容投影到組件後的Angular調用的AfterContentInitAfterContentChecked掛鉤。

內容投影

內容投影是一種從組件外部導入HTML內容並將該內容插入組件模板中指定位置的方法。

Angular 1開發人員知道這種技術是跨越式的。

考慮之前的AfterView示例中的這種變化。 這一次,它不是在模板中包含子視圖,而是從AfterContentComponent的父項導入內容。 這是父母的模板。

lib/src/after_content_component.dart (template excerpt)

template: '''
  <div class="parent">
    <h2>AfterContent</h2>

    <div *ngIf="show">
      <after-content>
        <my-child></my-child>
      </after-content>
    </div>

    <h4>-- AfterContent Logs --</h4>
    <p><button (click)="reset()">Reset</button></p>
    <div *ngFor="let msg of logs">{{msg}}</div>
  </div>
  ''',

請注意,<my-child>標籤隱藏在<after-content>標籤之間。 除非您打算將該內容投影到組件中,不然毫不要在組件的元素標籤之間放置內容。

如今看看組件的模板:

lib/src/after_content_component.dart (template)

template: '''
  <div>-- projected content begins --</div>
    <ng-content></ng-content>
  <div>-- projected content ends --</div>
  <p *ngIf="comment.isNotEmpty" class="comment">{{comment}}</p>
  ''',

<ng-content>標記是外部內容的佔位符。 它告訴Angular在哪裏插入該內容。 在這種狀況下,投影內容是來自父級的<my-child>

內容投影的指示標記是(a)組件元素標籤之間的HTML和(b)組件模板中存在<ng-content>標籤。

AfterContent掛鉤

AfterContent掛鉤與AfterView掛鉤相似。 關鍵的區別在於子組件

  • AfterView鉤子涉及ViewChildren,子組件的元素標籤出如今組件的模板中。
  • AfterContent掛鉤涉及ContentChildren,Angular投射到組件中的子組件。

如下AfterContent掛鉤根據內容子代(只能經過使用@ContentChild註解的屬性查詢它)中的值進行更改。

lib/src/after_content_component.dart (class excerpts)

class AfterContentComponent implements AfterContentChecked, AfterContentInit {
  String _prevHero = '';
  String comment = '';

  // Query for a CONTENT child of type `ChildComponent`
  @ContentChild(ChildComponent)
  ChildComponent contentChild;

  ngAfterContentInit() {
    // contentChild is set after the content has been initialized
    _logIt('AfterContentInit');
    _doSomething();
  }

  ngAfterContentChecked() {
    // contentChild is updated after the content has been checked
    if (_prevHero == contentChild?.hero) {
      _logIt('AfterContentChecked (no change)');
    } else {
      _prevHero = contentChild?.hero;
      _logIt('AfterContentChecked');
      _doSomething();
    }
  }

  // ...
}

AfterContent沒有對單向流的擔心

該組件的doSomething方法當即更新組件的數據綁定comment屬性。 不須要等

回想一下,在調用AfterView鉤子以前,Angular調用了AfterContent的兩個鉤子。 在完成該組件的視圖以前,Angular會完成投影內容的組合。 AfterContent ...和AfterView ...鉤子之間有一個小窗口來修改宿主視圖。

相關文章
相關標籤/搜索