Angular生命周期解析

每个组件都有一个被Angular管理的生命周期,Angular提供了生命周期钩子,把这些关键生命时刻暴露出来,赋予我们在它们发生时采取行动的能力。

钩子 目的和时机
ngOnChanges() 当Angular(重新)设置数据绑定输入属性时响应。 该方法接受当前和上一属性值的SimpleChanges对象当被绑定的输入属性的值发生变化时调用,首次调用一定会发生在ngOnInit()之前。
ngOnInit() 在Angular第一次显示数据绑定和设置指令/组件的输入属性之后,初始化指令/组件。在第一轮ngOnChanges()完成之后调用,只调用一次。
ngDoCheck() 检测,并在发生Angular无法或不愿意自己检测的变化时作出反应。在每个Angular变更检测周期中调用,ngOnChanges()和ngOnInit()之后.
ngAfterContentInit() 当把内容投影进组件之后调用。第一次ngDoCheck()之后调用,只调用一次。只适用于组件。
ngAfterContentChecked() 每次完成被投影组件内容的变更检测之后调用,ngAfterContentInit()和每次ngDoCheck()之后调用只适合组件。
ngAfterViewInit() 每次做完组件视图和子视图的变更检测之后调用。ngAfterViewInit()和每次ngAfterContentChecked()之后调用。只适合组件。
ngAfterViewChecked() 每次做完组件视图和子视图的变更检测之后调用。ngAfterViewInit()和每次ngAfterContentChecked()之后调用。只适合组件。
ngOnDestroy() 当Angular每次销毁指令/组件之前调用并清扫。 在这儿反订阅可观察对象和分离事件处理器,以防内存泄漏。在Angular销毁指令/组件之前调用。

下面我们来看代码

// 子组件
import {AfterContentChecked,AfterContentInit,AfterViewChecked,AfterViewInit,
  DoCheck,  OnChanges, OnDestroy, OnInit,SimpleChange} from '@angular/core';

import { Component, Input } from '@angular/core';
import { LoggerService }    from './logger.service';

let nextId = 1;

export class PeekABoo implements OnInit {
  constructor(private logger: LoggerService) { }

  // 在组件首次加载的时候执行,在第一次ngOnchange后
  ngOnInit() { this.logIt(`OnInit`); }

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

@Component({
  selector: 'peek-a-boo',
  template: '<p>Now you see my hero, {{name}}</p>',
  styles: ['p {background: LightYellow; padding: 8px}']
})

export class PeekABooComponent extends PeekABoo implements
             OnChanges, OnInit, DoCheck,
             AfterContentInit, AfterContentChecked,
             AfterViewInit, AfterViewChecked,
             OnDestroy {
  @Input()  name: string;

  private verb = 'initialized';

  constructor(logger: LoggerService) {
    super(logger);

    let is = this.name ? 'is' : 'is not';
    this.logIt(`name ${is} known at construction`);
  }

  // 当父组件的变量变化时,自组建的属性也会改变.
  ngOnChanges(changes: SimpleChanges) {
    let changesMsgs: string[] = [];
    for (let propName in changes) {
      if (propName === 'name') {
        let name = changes['name'].currentValue;
        changesMsgs.push(`name ${this.verb} to "${name}"`);
      } else {
        changesMsgs.push(propName + ' ' + this.verb);
      }
    }
    this.logIt(`OnChanges: ${changesMsgs.join('; ')}`);
    this.verb = 'changed'; // next time it will be a change
  }

  // 在每次需要做出改变的时候的触发
  ngDoCheck() { this.logIt(`DoCheck`); }

  // 组件内容初始化,只在组件周期发生一次
  ngAfterContentInit() { this.logIt(`AfterContentInit`);  }

  // 在ngoncheck之后发生
  ngAfterContentChecked() { this.logIt(`AfterContentChecked`); }

  ngAfterViewInit() { this.logIt(`AfterViewInit`); }

  ngAfterViewChecked() { this.logIt(`AfterViewChecked`); }

  // 组件销毁
  ngOnDestroy() { this.logIt(`OnDestroy`); }
}

service代码是输出打印信息的

import { Injectable } from '@angular/core';

@Injectable()
export class LoggerService {
  logs: string[] = [];
  prevMsg = '';
  prevMsgCount = 1;

  log(msg: string)  {
    if (msg === this.prevMsg) {
      // 若之前的msg和新的msg相同时,count加1.
      this.logs[this.logs.length - 1] = msg + ` (${this.prevMsgCount += 1}x)`;
    } else {
      // New message; log it.
      this.prevMsg = msg;
      this.prevMsgCount = 1;
      this.logs.push(msg);
    }
  }

  clear() { this.logs.length = 0; }

  tick() {  this.tick_then(() => { }); }
  tick_then(fn: () => any) { setTimeout(fn, 0); }
}

我们很奇怪的子组件是如何来使用services的,来看父组件

// 父组件
import { Component } from '@angular/core';
import { LoggerService } from './logger.service';

@Component({
  selector: 'peek-a-boo-parent',
  template: `
  <div class="parent">
    <h2>Peek-A-Boo</h2>
    // 控制子组件的显影
    <button (click)="toggleChild()">
      {{hasChild ? 'Destroy' : 'Create'}} PeekABooComponent
    </button>
    <button (click)="updateHero()" [hidden]="!hasChild">Update Hero</button>

    <peek-a-boo *ngIf="hasChild" [name]="heroName">
    </peek-a-boo>

    <h4>-- Lifecycle Hook Log --</h4>
    <div *ngFor="let msg of hookLog">{{msg}}</div>
  </div>
  `,
  styles: ['.parent {background: moccasin}'],
  providers:  [ LoggerService ]  //父组件的service可供子组件使用
})
export class PeekABooParentComponent {

  hasChild = false;
  hookLog: string[];

  heroName = 'Windstorm';
  private logger: LoggerService;

  constructor(logger: LoggerService) {
    this.logger = logger;
    this.hookLog = logger.logs;
  }

  toggleChild() {
    this.hasChild = !this.hasChild;
    if (this.hasChild) {
      this.heroName = 'Windstorm';
      this.logger.clear(); // clear log on create
    }
    this.logger.tick();
  }

  updateHero() {
    this.heroName += '!';
    this.logger.tick();
  }
}

下面我们来看一下好玩的东西。。。

import { Directive, OnInit, OnDestroy } from '@angular/core';

import { LoggerService } from './logger.service';

let nextId = 1;

// 在这里定义了一个指令,上面有2个钩子函数
@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}`);
  }
}

究竟上面的代码有何用意?

// spy.component.html
<div class="parent">
  <h2>Spy Directive</h2>

  <input [(ngModel)]="newName" (keyup.enter)="addHero()">
  <button (click)="addHero()">Add Hero</button>
  <button (click)="reset()">Reset Heroes</button>

  <p></p>
  //这一步很关键,这些个div具有了生命周期钩子,从myspy获得
  <div *ngFor="let hero of heroes" mySpy class="heroes">
    {{hero}}
  </div>
  <h4>-- Spy Lifecycle Hook Log --</h4>
  <div *ngFor="let msg of spyLog">{{msg}}</div>
</div>

@Component({
  selector: 'spy-parent',
  templateUrl: './spy.component.html',
  styles: [
     '.parent {background: khaki;}',
     '.heroes {background: LightYellow; padding: 0 8px}'
  ],
  providers:  [LoggerService]
})
export class SpyParentComponent {
  newName = 'Herbie';
  heroes: string[] = ['Windstorm', 'Magneta'];
  spyLog: string[];

  constructor(private logger: LoggerService) {
    this.spyLog = logger.logs;
  }

  addHero() {
    if (this.newName.trim()) {
      this.heroes.push(this.newName.trim());
      this.newName = '';
      this.logger.tick();
    }
  }
  removeHero(hero: string) {
    this.heroes.splice(this.heroes.indexOf(hero), 1);
    this.logger.tick();
  }
  reset() {
    this.logger.log('-- reset --');
    this.heroes.length = 0;
    this.logger.tick();
  }
}

点击add时,添加一个英雄,会打印ngoninit

我们接着来看一个很好解释ngonchange的例子。

// parent.html
<div class="parent">
  <h2>{{title}}</h2>

  <table>
    <tr><td>Power: </td><td><input [(ngModel)]="power"></td></tr>
    <tr><td>Hero.name: </td><td><input [(ngModel)]="hero.name"></td></tr>
  </table>
  <p><button (click)="reset()">Reset Log</button></p>

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

下面这个代码很长,仔细看

import {
  Component, Input, OnChanges,
  SimpleChanges, ViewChild
} from '@angular/core';

class Hero {
  constructor(public name: string) {}
}

@Component({
  selector: 'on-changes',
  template: `
  <div class="hero">
    <p>{{hero}} can {{power}}</p>

    <h4>-- Change Log --</h4>
    <div *ngFor="let chg of changeLog">{{chg}}</div>
  </div>
  `,
  styles: [
    '.hero {background: LightYellow; padding: 8px; margin-top: 8px}',
    'p {background: Yellow; padding: 8px; margin-top: 8px}'
  ]
})
export class OnChangesComponent implements OnChanges {
  @Input() hero: Hero;
  @Input() power: string;

  changeLog: string[] = [];

  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}`);
    }
  }

  reset() { this.changeLog.length = 0; }
}

/***************************************/

@Component({
  selector: 'on-changes-parent',
  templateUrl: './on-changes-parent.component.html',
  styles: ['.parent {background: Lavender;}']
})
export class OnChangesParentComponent {
  hero: Hero;
  power: string;
  title = 'OnChanges';
  @ViewChild(OnChangesComponent) childView: OnChangesComponent;

  constructor() {
    this.reset();
  }

  reset() {
    // new Hero object every time; triggers onChanges
    this.hero = new Hero('Windstorm');
    // setting power only triggers onChanges if this value is different
    this.power = 'sing';
    if (this.childView) { this.childView.reset(); }
  }
}

有关viewChild在以前的章节已经讲过,可以回看之前的文章。需要注意的是这种吧2个组件写在一起的形式。
上面讲述了3个生命周期钩子函数调用的例子,还需自己去体味才行。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,904评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,581评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,527评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,463评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,546评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,572评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,582评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,330评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,776评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,087评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,257评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,923评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,571评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,192评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,436评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,145评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,997评论 25 707
  • Angular 2架构总览 - 简书http://www.jianshu.com/p/aeb11061b82c A...
    葡萄喃喃呓语阅读 1,484评论 2 13
  • 文章翻译已征得原作者同意,原文链接:link 正文 最近在stackoverflow上总看有人问到使用Angula...
    shh苏哈哈阅读 2,161评论 0 2
  • 我想和有趣的人生活 “人生一世,草木一秋”我想在余下的生命里。与有趣的人一起生活,感受生活的魅力! 有趣的人!有很...
    美味可口李子阅读 150评论 0 0
  • 感恩妈妈头晕能很快好起来,感恩父母在我身边,让我有机会尽一份孝心,感恩他们为我做的一切,无怨无悔。 感恩亲爱的女儿...
    LLXYXR阅读 171评论 0 0