这里分为了三个阶段,组件初始化阶段,变化检测,组件销毁。
会在组件初始化后看到组件,在变化检测阶段让属性值和页面展示保持一致。
一共只有9个方法。
生命周期的例子
新建一个项目demo3
新建组件ng g component life
export @H_403_18@class LifeComponent @H_403_18@implements OnInit {
constructor() { }
ngOnInit() {
}
}
在生产的组件中已经实现了OnInit接口,每一个钩子都是@angular/core中的一个接口,每一个接口都有一个唯一的钩子方法,比如OnInit接口的钩子方法是ngOnInit()。
从纯技术来讲,接口对于JavaScript和TypeScript来说都是可选的,因为JavaScript没有接口,angular在运行是看不到TypeScript接口,因为在编译成JavaScript的时候已经消失了。
但还是强烈建议添加上接口,这样可以获得IDE的支持,以及强类型的检查。。。
现在把所有的钩子接口都实现一下。
import {
Component,OnInit,OnChanges,DoCheck,AfterContentInit,AfterContentChecked,AfterViewInit,AfterViewChecked,OnDestroy,SimpleChanges,Input
} from '@angular/core';
let logIndex:number=1;
@Component({
selector: 'app-life',templateUrl: './life.component.html',styleUrls: ['./life.component.css']
})
export @H_403_18@class LifeComponent @H_403_18@implements OnInit,OnChanges,DoCheck,AfterContentInit,AfterContentChecked,AfterViewInit,AfterViewChecked,OnDestroy {
@Input()
name:string;
logIt(msg:string){
console.log(`#${logIndex++} ${msg}`);
}
constructor() {
@H_403_18@this.logIt("name属性在constructor里的值是:"+name);
}
ngOnChanges(changes: SimpleChanges): @H_403_18@void {
let name=changes['name'].currentValue;
@H_403_18@this.logIt("name属性在ngOnChanges里的值是:"+name);
}
ngOnInit() {
@H_403_18@this.logIt("ngOnInit");
}
ngDoCheck(): @H_403_18@void {
@H_403_18@this.logIt("ngDoCheck");
}
ngAfterContentInit(): @H_403_18@void {
@H_403_18@this.logIt("ngAfterContentInit");
}
ngAfterContentChecked(): @H_403_18@void {
@H_403_18@this.logIt("ngAfterContentChecked");
}
ngAfterViewInit(): @H_403_18@void {
@H_403_18@this.logIt("ngAfterViewInit");
}
ngAfterViewChecked(): @H_403_18@void {
@H_403_18@this.logIt("ngAfterViewChecked");
}
ngOnDestroy(): @H_403_18@void {
@H_403_18@this.logIt("ngOnDestroy");
}
}
修改app.component.html
<app-life [name]="title"></app-life>
修改app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',templateUrl: './app.component.html',styleUrls: ['./app.component.css']
})
export @H_403_18@class AppComponent {
title = 'Tom';
}
启动项目
界面显示life works!
控制台显示信息为
#1 name属性在constructor里的值是:
#2 name属性在ngOnChanges里的值是:Tom
#3 ngOnInit
#4 ngDoCheck
#5 ngAfterContentInit
#6 ngAfterContentChecked
#7 ngAfterViewInit
#8 ngAfterViewChecked
Angular is running in the development mode. Call enableProdMode() to enable the production mode.
#9 ngDoCheck
#10 ngAfterContentChecked
#11 ngAfterViewChecked
- ngOnChanges:当一个父组件修改或初始化一个子组件的输入属性的时候被调用
- ngOnInit:初始化(如果初始化的逻辑需要依赖输入属性,那就一定要写在ngOnInit中,而不要写在构造函数中)
- ngDoCheck:用来检测
- ngAfterContentInit:
- ngAfterContentChecked:
- ngAfterViewInit
- ngAfterViewChecked
- ngDoCheck
- ngAfterContentChecked
- ngAfterViewChecked
ngOnchanges
可变对象,不可变对象
字符串是不可变的
对象的值是可变的
新建组件ng g component child
修改child.component.ts
import {Component,Input,SimpleChanges} from '@angular/core';
@Component({
selector: 'app-child',templateUrl: './child.component.html',styleUrls: ['./child.component.css']
})
export @H_403_18@class ChildComponent @H_403_18@implements OnInit,OnChanges {
ngOnChanges(changes: SimpleChanges): @H_403_18@void {
console.log(JSON.stringify(changes,@H_403_18@null,2));
}
@Input()
greeting: string;
@Input()
user:{name:string};
message:string="初始化消息";
constructor() {
}
ngOnInit() {
}
}
修改child.component.html
<div class="child"> <h2>我是子组件</h2> <div>问候语:{{greeting}}</div> <div>姓名:{{user.name}}</div> <div>消息:<input [(ngModel)]="message"></div> </div>
修改child.component.css
.child{ background:lightgreen; }
修改app.component.ts
import { Component } @H_403_18@from '@angular/core';
@Component({ selector: 'app-root',templateUrl: './app.component.html',styleUrls: ['./app.component.css'] })
export @H_403_18@class AppComponent { greeting:string="Hello"; user:{name:string}={name:"Tom"};
@H_403_18@constructor(){ } }
修改app.component.html
<div class="parent">
<h2>我是父组件</h2>
<div>
问候语:<input type="text" [(ngModel)]="greeting">
</div>
<div>
姓名:<inpu type="text" [(ngModel)]="user.name"></inpu>
</div>
<app-child [greeting]="greeting" [user]="user"></app-child>
</div>
修改app.component.css
.parent{ background:cyan; }
启动项目
报了个错,因为用到的双向绑定需要在app.module.ts
@H_403_18@import{FormsModule} @H_403_18@from '@angular/forms';
imports: [
BrowserModule,FormsModule
],
控制台的打印结果为
{
"greeting": { "currentValue": "Hello","firstChange": true },"user": { "currentValue": { "name": "Tom" },"firstChange": true } }
当我改变页面上父组件中问候语的值的时候,会打印出
{
"greeting": { "prevIoUsValue": "Hello","currentValue": "Hello1","firstChange": false } }
但当我改变页面上父组件中姓名的值的时候,不会打印出新东西。
因为greeting是字符串是不可变对象(每次值改变的时候都会创建一个新的字符串,然后把引用指向新的字符串),而user是可变对象,修改姓名的值的时候并没有改变user对象的引用。那么怎么监控可变对象呢,用doCheck
当我改变页面中子组件的消息时,也不会打印出新东西,因为子组件中的message属性没有被@Input()标记不是输入属性。
变更检测机制
查看package.json文件中的dependencies的zone.js
就是zone.js来实现变更检测机制的,主要作用是保证属性的变化和页面的变化是一致的,浏览器中发生的任何异步事件都会触发变更检测,比如点击按钮,输入数据。。。
默认的是Default策略,当父组件变化时会检测整个组件树
如果在子组件中设置了OnPush,当父组件变化时就只会检测父组件。
当孙子组件1发生变化后,红色的部分都会被检测一遍,也就是调DoCheck方法。并且是从父组件开始检查
修改child.component.ts
import {Component,DoCheck} from '@angular/core';
@Component({
selector: 'app-child',DoCheck{
@Input()
greeting: string;
@Input()
user:{name:string};
message:string="初始化消息";
oldUsername:string;
changeDetected:@H_403_18@boolean=@H_403_18@false;
noChangeCount:number=0;
constructor() {
}
ngOnInit() {
}
ngOnChanges(changes: SimpleChanges): @H_403_18@void {
console.log(JSON.stringify(changes,2));
}
ngDoCheck(): @H_403_18@void {
@H_403_18@if(@H_403_18@this.user.name!==@H_403_18@this.oldUsername){
@H_403_18@this.changeDetected=@H_403_18@true;
console.log("DoCheck:user.name从"+@H_403_18@this.oldUsername+"变为"+@H_403_18@this.user.name)
@H_403_18@this.oldUsername=@H_403_18@this.user.name;
}
@H_403_18@if(@H_403_18@this.changeDetected){
@H_403_18@this.noChangeCount=0;
}@H_403_18@else{
@H_403_18@this.noChangeCount=@H_403_18@this.noChangeCount+1;
console.log("DoCheck:user.name没变化时ngDoCheck方法已经被调用"+@H_403_18@this.noChangeCount)
}
@H_403_18@this.changeDetected=@H_403_18@false;
}
}
当我在问候语的输入框和姓名的输入框中来回切换点击的时候,就会触发docheck方法。当我改变姓名的值的时候也会触发。之后我再点击输入框的时候,调用次数重置为1。这就是上一段代码要实现的效果。
虽然当我修改姓名的时候这个钩子会被调用,但是我们必须要小心ngdocheck这个钩子会非常频繁的被调用,每一次变化都会被调用,在这个例子中,我还没做任何操作呢,只是在页面随便点点就会被调用好几次,只有很少的调用次数是修改数据的时候触发的。
所以对ngDoCheck这个方法的实现一定要非常高效,非常轻量级,不然会引起性能问题。不光是这个方法,变更检测中的那些带Check的方法都应该这样
view钩子
新建一个项目demo5
新建一个组件ng g component child
修改child.component.ts
import { Component,OnInit } from '@angular/core';
@Component({
selector: 'app-child',styleUrls: ['./child.component.css']
})
export @H_403_18@class ChildComponent @H_403_18@implements OnInit {
constructor() { }
ngOnInit() {
}
greeting(name:string){
console.log("hello"+name);
}
}
修改app.component.html
<app-child #child1></app-child>
<app-child #child2></app-child>
<button (click)="child2.greeting('Jerry')"></button>
修改app.component.ts
@H_403_18@import { Component,ViewChild } from '@angular/core';
@H_403_18@import {ChildComponent} from "./child/child.component";
@Component({
selector: 'app-root',templateUrl: './app.component.html',styleUrls: ['./app.component.css']
})
export @H_403_18@class AppComponent {
@ViewChild("child1")
child1:ChildComponent //获得子组件之后就可以调用子组件中的方法了
constructor(){}
ngOnInit():void{
@H_403_18@this.child1.greeting("Tom");//调用方法
}
}
启动项目
会在控制台打印出helloTom
然后我点击按钮,会打印出helloJerry
现在学习那两个钩子AfterViewInit,AfterViewChecked
修改child.component.ts
import { Component,AfterViewChecked} from '@angular/core';
@Component({
selector: 'app-child',AfterViewChecked {
ngAfterViewInit(): @H_403_18@void {
console.log("子组件的视图初始化完毕");
}
ngAfterViewChecked(): @H_403_18@void {
console.log("子组件的视图变更检测完毕");
}
constructor() { }
ngOnInit() {
}
greeting(name:string){
console.log("hello"+name);
}
}
修改app.component.ts
import { Component,ViewChild,AfterViewChecked} from '@angular/core';
import {ChildComponent} from "./child/child.component";
@Component({
selector: 'app-root',styleUrls: ['./app.component.css']
})
export @H_403_18@class AppComponent @H_403_18@implements OnInit,AfterViewChecked{
//在组件模板的内容都已经呈现给用户看之后,会调用这两个方法
ngAfterViewInit(): @H_403_18@void {
console.log("父组件的视图初始化完毕");
}
ngAfterViewChecked(): @H_403_18@void {
console.log("父组件的视图变更检测完毕");
}
@ViewChild("child1")
child1:ChildComponent
constructor(){}
ngOnInit():@H_403_18@void{
setInterval(()=>{
@H_403_18@this.child1.greeting("Tom");
},5000);
}
}
启动项目,会在控制台打印出
子组件的视图初始化完毕
子组件的视图变更检测完毕
子组件的视图初始化完毕
子组件的视图变更检测完毕
父组件的视图初始化完毕
父组件的视图变更检测完毕
Angular @H_403_18@is running @H_403_18@in @H_403_18@the development mode. Call enableProdMode() @H_403_18@to enable @H_403_18@the production mode.
子组件的视图变更检测完毕
子组件的视图变更检测完毕
父组件的视图变更检测完毕
helloTom
子组件的视图变更检测完毕
子组件的视图变更检测完毕
父组件的视图变更检测完毕
helloTom
子组件的视图变更检测完毕
子组件的视图变更检测完毕
父组件的视图变更检测完毕
helloTom
子组件的视图变更检测完毕
子组件的视图变更检测完毕
父组件的视图变更检测完毕
helloTom
- 初始化的方法会在变更检测方法前面被调用。都是在视图组装完毕之后被调用的
- 不要在这两个方法中去改变视图中绑定的东西,如果想改变也要写在一个setTimeout里边
- 初始化方法只会被调用一次
- 如果有子组件,会先弄完子组件的然后再弄父组件的。(这里弄了两个子组件所以子组件调用了两次)
- 如果想实现ngAfterViewChecked这个钩子,方法一定要非常高效,非常轻量级,不然会引起性能问题
修改app.component.ts,
message:string;
ngAfterViewInit(): @H_403_18@void {
console.log("父组件的视图初始化完毕");
@H_403_18@this.message="Hello";
}
修改app.component.html
<app-child #child1></app-child> <app-child #child2></app-child> <button (click)="child2.greeting('Jerry')">调用child2的greeting方法</button> {{message}}
启动项目,这时候会报一个错
ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed @H_403_18@after @H_403_18@it was checked. PrevIoUs value: 'undefined'. Current value: 'Hello'.
因为Angular规定一个视图在组装好之后再去更新这个视图
而ngAfterViewInit就是在视图组装好之后被触发的
解决办法
ngAfterViewInit(): void {
console.log("父组件的视图初始化完毕");
setTimeout(()=>{ @H_403_18@this.message="Hello"; },0); }
让其在JavaScript的另一个运行周期中去运行