understand angular life cycle hooks - ltoddy/blog GitHub Wiki

理解Angular的生命周期

在Angular的应用中,从创建到销毁,组件的整个生命周期都由Angular管理.它使我们能够访问生命周期钩子函数,从而让我们 在组件的生命周期的关键时刻采取行动.

为了使用这些钩子函数,我们必须告诉Angular我们想要实现所需的钩子函数接口.Angular检查组件类并调用钩子函数(如果定义了的话), 例如(OnInit):

export class MyComponent implements OnInit {
  constructor() {}

  ngOnInit() {
    // 业务逻辑
  }
}

这是Angular提供的钩子函数列表,这些钩子函数以这种确切的顺序被调用.

lifecycle hooks
ngOnChanges()
ngOnInit()
ngDoCheck()
ngAfterContentInit()
ngAfterContentChecked()
ngAfterViewInit()
ngAfterViewChecked()
ngOnDestroy()

ngOnChanges

每次创建组件时都会调用此方法,然后当输入属性(@Input)被改变时,此方法也都会被调用. 它接受一个SimpleChanges对象作为参数,该对象包含有关输入属性更改的信息:之前的值和现在的值.

export class MyComponent implements OnChanges {
  ngOnChanges(changes: SimpleChanges) {
    // 业务逻辑
  }
}

如果你需要根据接收到的输入属性处理组件中的任何特定逻辑,这个钩子函数是非常有用的.

假设有一个显示用户信息的组件,这个组件接受UserInfo对象为输入参数.下面是一个有关如何在该组件中 使用ngOnChanges来添加逻辑以处理UserInfo的例子:

export class UserInfoComponent implements OnChanges {
  @Input() userInfo: UserInfo;

  ngOnChanges(changes: SimpleChanges) {
    const prevValue = changes['userInfo'].previouseValue;
    const currValue = changes['userInfo'].currentValue;
    // 业务逻辑
  }
}

ngOnInit

这个钩子函数只会在整个生命周期中调用一次,在第一次调用ngOnChanges之后调用. 此时,在这个钩子函数中,您不仅可以访问数据绑定属性,还可以访问组件的输入属性.

export class MyComponent implements OnInit {
  ngOnInit() {
    // 业务逻辑
  }
}

这个钩子函数在Angular的生命周期钩子中最常用的钩子函数之一.在这里,你可以调用异步操作,比如发送请求,也可以为要由 该组件处理的表单创建FormGroup,设置订阅等等.基本上,在这里,你可以在构造组件后不久执行任何初始化.

假设你有一个注册表单的组件,并且想根据从后端服务请求的数据创建表单.以下是关于如何使用ngOnInit实现 此目的的例子:

export class RegisterFormComponent implements OnInit {
  formGroup: FormGroup;

  constructor(private backendService: BackendService) {}

  ngOnInit() {
    this.backendService.getFormData()
      .subscribe(response => {
        this.formGroup = this.createForm(response.data);
      });
  }

  private createForm(formFields: Array<FormField>): FormGroup {
    // 创建FormGroup逻辑
  }
}

constructor vs ngOnInit

或许,你可能想知道为什么将初始化的逻辑放在ngOnInit中而不是构造器中,好吧,构造函数最好是留给依赖注入,而不添加任何业务逻辑, 我们的初始化逻辑就应该放在ngOnInit上.

那是因为,构造函数是由JavaScript引擎处理,而不是Angular.这也是创建ngOnInit的原因之一,该钩子函数被Angular调用, 并成为由它管理的组件生命周期的一部分.另外,当然,还不能在构造函数上访问输入属性.

ngDoCheck

这个钩子函数可以被视为ngOnChanges的扩展.可以使用此方法来检测Angular无法或者不会检测到的更改.在ngOnChangesngOnInit 调用之后,每次更改检测都会调用它.

export class MyComponent implements DoCheck {
  curValue;
  prevValue;
  changeDetected: boolean = false;

  ngDoCheck() {
    if (this.prevValue !== this.currValue) {
      this.changeDetected = true;
      // 业务逻辑
    }
  }
}

这个钩子函数是非常的昂贵的,因为它被频繁的调用,在每个变更检测周期之后,无论变更发生在那里,都会调用.因此,应该小心的使用此钩子函数, 避免影响用户的体验.

AfterContent 和 AfterView

在讨论这个之前,首先需要了解它们与什么相关.

AfterContent钩子与ContentChild有关,ContentChild是Angular映射到组件中的子组件.

AfterView钩子是与ViewChild有关,ViewChild是其显示在组件模板中的子组件.

假设我们下面有同时包含ContentChildViewChild的组件.我们有一个ng-content标签,它将呈现从父级传递来的内容, 以及对ViewChildren的引用(称为包装器).

@Component({
  selector: 'app-my-component',
  template: `
    <div #wrapper>
      <ng-content></ng-content>
    </div>
  `
})
export class MyComponent {
  @ViewChild('wrapper') wrapper: ElementRef;
  @ContentChild('content') content: ElementRef;
}

ngAfterContentInit

ngDoCheck第一次调用之后,这个钩子函数在组件的生命周期中仅被调用一次.在这个钩子函数中,在组件创建之后, 我们将第一次访问ContentChildElementRef;在Angular将外部内容映射到组件视图之后.

@Component({
  selector: 'app-my-component',
  template: `
    <div>
      <ng-content></ng-content>
    </div>
  `
})
export class MyComponent implements AfterContentInit {
  @ContentChild('content') content: ElementRef;

  ngAfterContentInit() {
    // 现在可以访问: this.content
    // 业务逻辑
  }
}

ngAfterContentChecked

在组件的生命周期里,这个方法在ngAfterContentInit之后被调用一次,然后在随后的每个ngDoCheck之后被调用,

@Component({
  selector: 'app-my-component',
  template: `
    <div>
      <ng-content></ng-content>
    </div>
  `
})
export class MyComponent implements AfterContentChecked {
  @ContentChild('content') content: ElementRef;

  ngAfterContentChecked() {
    // 我们可以访问this.content,内容已被检查过了
    // 业务逻辑
  }
}

ngAfterViewInit

在组件生命周期里,这个钩子函数仅在ngAfterContentChecked调用之后调用一次.在此方法中,可以首次访问ViewChildElementRef.

@Component({
  selector: 'app-my-component',
  template: `
    <div #wrapper >
      ...
    </div>
  `
})
export class MyComponent implements AfterViewInit {
  @ViewChild('wrapper') wrapper: ElementRef;

  ngAfterViewInit() {
    // 现在,可以访问this.wrapper
    // 业务逻辑
  }

}

当需要根据视图的组件加载视图上的内容时,此钩子函数是非常有用的.例如,当需要设置视频播放或者通过canvas元素创建图标时. 下面是有关如何使用ngAfterViewInit钩子设置图标的例子:

@Component({
  selector: 'app-my-component',
  template: `
    <div>
      <canvas id="myCanvas" ></canvas>
    </div>
  `
})
export class MyComponent implements AfterViewInit {
  ngAfterViewInit() {
    // 现在可以通过id来获得canvas元素来创建图表
    this.chart = new Chart('radarCanvas', {
      ...
    });
  }
}

ngAfterViewChecked

ngAfterViewInit之后调用此方法一次,然后在后续的ngAfterContentChecked之后调用此方法.

@Component({
  selector: 'app-my-component',
  template: `
    <div #wrapper >
      ...
    </div>
  `
})
export class MyComponent implements AfterViewChecked {
  @ViewChild('wrapper') wrapper: ElementRef;

  ngAfterViewChecked() {
    // 这里我们可以访问this.wrapper,视图已经被检查过了
    // 业务逻辑
  }
}

ngOnDestroy

最后,在Angular销毁它之前,该钩子函数在组件的生命周期仅被调用一次. 在这个钩子函数中,应该放置该组件的所有清理逻辑,比如在这删除任何本地存储信息,取消订阅等,以避免内存泄漏.

export class MyComponent implements OnDestroy {
  private mySubject: Subject<string> = new Subject();

  ngOnDestroy() {
    localStorage.removeItem('storageKey');
    this.mySubject.unsubscribe();
  }
}

用户刷新页面或者关闭浏览器的时候,不会调用ngOnDestroy,因此,如果还需要在这些情况下处理一些清理逻辑. 则可以使用HostListener装饰器:

@HostListener('window:beforeunload')
ngOnDestroy() {
 // 清理逻辑
}

在创建Angular应用程序时,了解Angular生命周期,它们的目标以及何时调用它们将非常有用. 因此,重要的是要了解它们的工作原理以及可以从中获得什么,以便能够在需要时用它.

⚠️ **GitHub.com Fallback** ⚠️