Angular4-在线竞拍应用-组件的生命周期

前端之家收集整理的这篇文章主要介绍了Angular4-在线竞拍应用-组件的生命周期前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

红色的被调用一次,绿色的会被调用多次。

这里分为了三个阶段,组件初始化阶段,变化检测,组件销毁

会在组件初始化后看到组件,在变化检测阶段让属性值和页面展示保持一致。

变化检测中的四个方法和组件初始化中的四个方法是一样的。

一共只有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的另一个运行周期中去运行

猜你在找的Angularjs相关文章