Angular 默认提供生命周期钩子ngOnInit
。
如果我们已经有一个constructor
,为什么要使用ngOnInit
?
Constructor
是在实例化类时执行的类的默认方法,并确保在类及其子类中正确初始化字段。 Angular 或更好的 Dependency Injector(DI)分析构造函数参数,当它通过调用new MyClass()
创建新实例时,它会尝试查找与构造函数参数类型匹配的提供程序,解析它们并将它们传递给构造函数,如
new MyClass(someArg);
ngOnInit
是ngOnInit
调用的生命周期钩子,表示 Angular 已完成创建组件。
我们必须导入OnInit
以便像这样使用(实际上实现OnInit
不是强制性的,但被认为是良好的做法):
import {Component, OnInit} from '@angular/core';
然后使用OnInit
的方法我们必须在类中实现。
export class App implements OnInit{
constructor(){
//called first time before the ngOnInit()
}
ngOnInit(){
//called after the constructor and called after the first ngOnChanges()
}
}
在初始化指令的数据绑定属性后,实现此接口以执行自定义初始化逻辑。在第一次检查指令的数据绑定属性之后,以及在检查其任何子项之前,立即调用 ngOnInit。实例化指令时仅调用一次。
大多数情况下,我们使用ngOnInit
进行所有初始化 / 声明,并避免在构造函数中工作。构造函数应仅用于初始化类成员,但不应该执行实际的 “工作”。
所以你应该使用constructor()
来设置依赖注入,而不是其他。 ngOnInit()是 “开始” 的最佳位置 - 它是解析组件绑定的地方 / 时间。
有关更多信息,请参阅:
文章Consularctor 和 Angular 中的 ngOnInit 之间的本质区别探讨了多个视角的差异。这个答案提供了与组件初始化过程相关的最重要的差异解释,它也显示了不同的用法。
Angular bootstrap 过程包括两个主要阶段:
当 Angular 构造组件树时,将调用组件的构造函数。所有生命周期挂钩都被称为运行更改检测的一部分。
当 Angular 构造组件树时,已经配置了根模块注入器,因此您可以注入任何全局依赖项。此外,当 Angular 实例化子组件类时,父组件的注入器也已设置,因此您可以注入在父组件上定义的提供程序,包括父组件本身。组件构造函数是在注入器上下文中调用的唯一方法,因此如果您需要任何依赖项,那么这是获取这些依赖项的唯一位置。
当 Angular 开始更改检测时,构造组件树并调用树中所有组件的构造函数。此外,每个组件的模板节点都会添加到 DOM 中。在更改检测期间处理@Input
通信机制,因此您不能期望在构造函数中具有可用的属性。它将在ngOnInit
之后ngOnInit
。
让我们看一个简单的例子。假设您有以下模板:
<my-app>
<child-comp [i]='prop'>
所以 Angular 开始引导应用程序。正如我所说,它首先为每个组件创建类。所以它调用MyAppComponent
构造函数。它还创建了一个 DOM 节点,它是my-app
组件的主机元素。然后继续为child-comp
创建一个 host 元素并调用ChildComponent
构造函数。在这个阶段,它并不真正关心i
输入绑定和任何生命周期钩子。因此,当此过程完成时,Angular 最终会得到以下组件视图树:
MyAppView
- MyApp component instance
- my-app host element data
ChildCompnentView
- ChildComponent component instance
- child-comp host element data
然后才运行更改检测并更新my-app
绑定,并在 MyAppComponent 类上调用ngOnInit
。然后继续更新child-comp
的绑定并在 ChildComponent 类上调用ngOnInit
。
您可以在构造函数或ngOnInit
执行初始化逻辑,具体取决于您需要的内容。例如,文章以下是在评估 @ViewChild 查询之前如何获取 ViewContainerRef,以显示在构造函数中可能需要执行的初始化逻辑类型。
以下是一些有助于您更好地理解该主题的文章:
我认为最好的例子是使用服务。假设当我的组件被 “激活” 时,我想从我的服务器获取数据。假设我从服务器获取数据之后还想对数据做一些额外的事情,也许我得到一个错误并希望以不同的方式记录它。
使用 ngOnInit 对构造函数来说非常简单,它还限制了我需要向应用程序添加多少个回调层。
例如:
export class Users implements OnInit{
user_list: Array<any>;
constructor(private _userService: UserService){
};
ngOnInit(){
this.getUsers();
};
getUsers(){
this._userService.getUsersFromService().subscribe(users => this.user_list = users);
};
}
使用我的构造函数,我可以调用我的_userService 并填充我的 user_list,但也许我想用它做一些额外的事情。就像确保一切都是大写的一样,我不完全确定我的数据是如何通过的。
因此,使用 ngOnInit 变得更加容易。
export class Users implements OnInit{
user_list: Array<any>;
constructor(private _userService: UserService){
};
ngOnInit(){
this.getUsers();
};
getUsers(){
this._userService.getUsersFromService().subscribe(users => this.user_list = users);
this.user_list.toUpperCase();
};
}
这使得它更容易看到,因此我在初始化时只需在我的组件中调用我的函数,而不是在其他地方挖掘它。实际上它只是您可以使用的另一种工具,以便将来更容易阅读和使用。另外我发现将函数调用放在构造函数中真的很糟糕!