当 Angular 实例化组件类 并渲染组件视图及其子视图时,组件实例的生命周期就开始了。生命周期一直伴随着变更检测,Angular 会检查数据绑定属性何时发生变化,并按需更新视图和组件实例。当 Angular 销毁组件实例并从 DOM 中移除它渲染的模板时,生命周期就结束了。当 Angular 在执行过程中创建、更新和销毁实例时,指令就有了类似的生命周期。
你的应用可以使用生命周期钩子方法来触发组件或指令生命周期中的关键事件,以初始化新实例,需要时启动变更检测,在变更检测过程中响应更新,并在删除实例之前进行清理。
- 浅灰色名字的事件,在组件的生命周期中只会触发一次,而绿色的随着相应的逻辑变化会多次触发。
Construction, OnChanges, OnInit, DoCheck, AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked, OnDestroy
1 响应生命周期事件
可以通过实现一个或多个 Angular core
库中定义的生命周期钩子接口来响应组件或指令生命周期中的事件。这些钩子让你有机会在适当的时候对组件或指令实例进行操作,比如 Angular 创建、更新或销毁这个实例时。
每个接口都有唯一的一个钩子方法,它们的名字是由接口名再加上 ng 前缀构成的。比如,OnInit 接口的钩子方法叫做 ngOnInit()。如果你在组件或指令类中实现了这个方法,Angular 就会在首次检查完组件或指令的输入属性后,紧接着调用它。
1.1 constructor()
每一个组件文件,本质上都是一个类,而constructor是ES6引入类的概念后新出现的东西,是类的自身属性,所以并非Angular的钩子,constructor的主要作用是注入依赖,这里就不展开了。这是最先被调用的一个函数。Angular 在实例化组件类时执行,可以用来接收 Angular 注入的服务实例对象。
import {Component} from '@angular/core';// 引入服务
import {StorageService} from '../../services/storage.service'@Component({selector: 'app-life',templateUrl: './life.component.html',styleUrls: ['./life.component.scss']
})
export class LifeComponent {constructor(public StorageService: StorageService) {console.log('init StorageService', this.StorageService)}
}
1.2 ngOnChanges()
注意这里解释一下什么是输入属性?如下:
Angular的输入属性是指带**有@Input()**装饰器的可设置属性,它允许父组件将数据值发送到子组件中。具体来说,父组件通过绑定属性到子组件,子组件则利用@Input()装饰器来标识这是一个输入变量属性。
一旦检测到该组件或指令的输入属性发生了变化,Angular 就会调用它的 ngOnChanges() 方法。这个 onChanges 范例通过监控 OnChanges() 钩子演示了这一点。
如下例子所示:
父组件:
<app-life [parentValue]="parentValue"></app-life>
<input type="text" name="parentValue" [(ngModel)]="parentValue">
子组件:
import {Component, Input, SimpleChanges} from '@angular/core';// 引入服务
import {StorageService} from '../../services/storage.service'@Component({selector: 'app-life',templateUrl: './life.component.html',styleUrls: ['./life.component.scss']
})
export class LifeComponent {constructor(public StorageService: StorageService) {console.log('init StorageService', this.StorageService)}@Input() parentValue: string;ngOnChanges(changes: SimpleChanges) {console.log(changes)for (const propName in changes) {const chng = changes[propName];const cur = JSON.stringify(chng.currentValue);const prev = JSON.stringify(chng.previousValue);console.log('cur = ', cur)console.log('prev = ', prev)}}
}
当输入属性的值发生变化时:
ngOnChanges() 方法获取了一个对象,它把每个发生变化的属性名都映射到了一个SimpleChange对象,该对象中有属性的当前值和前一个值。这个钩子会在这些发生了变化的属性上进行迭代,并记录它们。
1.3 ngOnInit()
通常我们在这里获取数据或者设置属性值。
ngOnInit() {this.getUserDataByAPIRequest();this.setOtherData();this.age = 18;
}
使用 ngOnInit() 方法执行以下初始化任务:
-
在构造函数外部执行复杂的初始化。**组件的构造应该既便宜又安全。比如,你不应该在组件构造函数中获取数据。**当在测试中创建组件时或者决定显示它之前,你不应该担心新组件会尝试联系远程服务器。ngOnInit()是组件获取初始数据的好地方。
你固然可以在构造函数中调用getHeroes(),但那不是最佳实践。让构造函数保持简单,只做最小化的初始化操作,比如把构造函数的参数赋值给属性。构造函数不应该做任何事。它当然不应该调用某个函数来向远端服务(比如真实的数据服务)发起HTTP 请求。而是选择在 ngOnInit 生命周期钩子中调用 getHeroes(),之后 Angular 会在构造出HeroesComponent 的实例之后的某个合适的时机调用 ngOnInit()。
ngOnInit(): void {this.getHeroes();
}
-
在 Angular设置好输入属性之后设置组件。构造函数应该只把初始局部变量设置为简单的值。请记住,只有在构造完成之后才会设置指令的数据绑定输入属性。如果要根据这些属性对指令进行初始化,请在运行ngOnInit() 时设置它们。
ngOnChanges() 方法是你能访问这些属性的第一次机会。Angular 会在调用 ngOnInit() 之前调用
ngOnChanges(),而且之后还会调用多次。但它只调用一次 ngOnInit()。
1.4 ngDoCheck()
这种情况不常见,如果是新手也没必要在这上面伤脑筋,只需要知道它会在ngOnInit()和每次ngOnChanges()之后调用。
ngDoCheck() {console.log('ngOnInit()和每次ngOnChanges()之后调用')}
1.5 ngAfterContentInit()
它会在 Angular 初始化完该指令的所有内容之后立即调用。要点:
- 在指令初始化完成之后,它只会调用一次。
- 可以用来处理一些初始化任务。
1.6 ngAfterContentChecked()
在默认的变更检测器对该指令下的所有内容完成了变更检测之后立即调用。
1.7 ngAfterViewInit()
简单来说就是当组件视图渲染完成后调用,也就是说在这个函数执行之前是没有办法获取到HTML里面的内容的。举个例子,我们想获取视图里的一个div结点, 可以通过ViewChild, 然后尝试在ngOnInit和ngAfterViewInit中去打印。
<app-life [parentValue]="parentValue" #lifeComponent></app-life>
import {Component, ViewChild} from '@angular/core';@Component({selector: 'app-root',templateUrl: './app.component.html',styleUrls: ['./app.component.scss']
})
export class AppComponent {title = 'firstProject';public parentValue: string = 'parentValue'@ViewChild('lifeComponent') lifeComponent: any;ngOnInit(): void {console.log('ngOnInit', this.lifeComponent);}ngAfterViewInit(): void {console.log('ngAfterViewInit', this.lifeComponent);}
}
1.8 ngAfterViewChecked()
<app-life [parentValue]="parentValue" #lifeComponent></app-life>
<input type="text" name="parentValue" [(ngModel)]="parentValue">
import {Component, ViewChild} from '@angular/core';@Component({selector: 'app-root',templateUrl: './app.component.html',styleUrls: ['./app.component.scss']
})
export class AppComponent {title = 'firstProject';public parentValue: string = 'parentValue'@ViewChild('lifeComponent') lifeComponent: any;ngOnInit(): void {console.log('ngOnInit', this.lifeComponent);}ngAfterViewInit(): void {console.log('ngAfterViewInit', this.lifeComponent);}ngAfterViewChecked(): void {console.log('ngAfterViewChecked', this.lifeComponent);}
}