Angular 默认提供生命周期钩子ngOnInit
。
为什么应该ngOnInit
使用,如果我们已经有一个constructor
?
Angular 默认提供生命周期钩子ngOnInit
。
为什么应该ngOnInit
使用,如果我们已经有一个constructor
?
这Constructor
是类的默认方法,在类被实例化时执行,并确保类及其子类中的字段的正确初始化。Angular 或更好的依赖注入器 (DI) 分析构造函数参数,当它通过调用它创建一个新实例时,它会new MyClass()
尝试找到与构造函数参数类型匹配的提供程序,解析它们并将它们传递给构造函数,例如
new MyClass(someArg);
ngOnInit
是 Angular 调用的生命周期钩子,用于指示 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() 是“开始”的更好地方——它是解决组件绑定的地方/时间。
有关更多信息,请参阅此处:
Angular 中 Constructor 和 ngOnInit 的本质区别一文从多个角度探讨了区别。这个答案提供了与组件初始化过程相关的最重要的差异解释,这也显示了用法的不同。
Angular 引导过程包括两个主要阶段:
当 Angular 构建组件树时,会调用组件的构造函数。所有生命周期挂钩都被称为运行变更检测的一部分。
当 Angular 构建组件树时,根模块注入器已经配置好,因此您可以注入任何全局依赖项。此外,当 Angular 实例化一个子组件类时,父组件的注入器也已经设置好,因此您可以注入在父组件上定义的提供程序,包括父组件本身。组件构造函数是在注入器上下文中调用的唯一方法,因此如果您需要任何依赖项,那么这是获取这些依赖项的唯一位置。
当 Angular 开始变更检测时,组件树被构建并且树中所有组件的构造函数都被调用。每个组件的模板节点也被添加到 DOM 中。在@Input
更改检测期间会处理通信机制,因此您不能期望在构造函数中具有可用的属性。它将在之后可用ngOnInit
。
让我们看一个简单的例子。假设您有以下模板:
<my-app>
<child-comp [i]='prop'>
所以 Angular 开始引导应用程序。正如我所说,它首先为每个组件创建类。所以它调用MyAppComponent
构造函数。它还创建了一个 DOM 节点,它是my-app
组件的宿主元素。然后它继续为child-comp
和调用ChildComponent
构造函数创建一个宿主元素。在这个阶段,它并不真正关心i
输入绑定和任何生命周期钩子。因此,当这个过程完成时,Angular 最终会得到以下组件视图树:
MyAppView
- MyApp component instance
- my-app host element data
ChildCompnentView
- ChildComponent component instance
- child-comp host element data
然后才运行更改检测并更新MyAppComponent 类上的my-app
和调用的绑定。ngOnInit
然后它继续更新ChildComponent 类上的child-comp
和调用的绑定。ngOnInit
您可以在构造函数中执行初始化逻辑,也可以ngOnInit
根据需要使用什么。例如文章Here is how to get ViewContainerRef before @ViewChild query is评估显示了在构造函数中可以要求执行什么类型的初始化逻辑。
这里有一些文章可以帮助您更好地理解该主题:
我认为最好的例子是使用服务。假设我想在我的组件被“激活”时从我的服务器中获取数据。假设我还想在从服务器获取数据后对数据做一些额外的事情,也许我得到一个错误并想以不同的方式记录它。
在构造函数上使用 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();
};
}
它使它更容易查看,因此我只需在初始化时在组件中调用我的函数,而不必在其他地方挖掘它。实际上,它只是您可以用来使其在未来更易于阅读和使用的另一种工具。此外,我发现将函数调用放在构造函数中是非常糟糕的做法!
好的,首先ngOnInit
是Angular 生命周期的一部分,而constructor
它是ES6 JavaScript 类的一部分,所以主要的区别就从这里开始!...
看看我创建的下面的图表,它显示了 Angular 的生命周期。
在 Angular2+ 中,我们用来为我们constructor
做这些DI(Dependency Injection)
,而在 Angular 1 中,它是通过调用 String 方法并检查注入了哪个依赖项来实现的。
正如您在上图中看到的,ngOnInit
在构造函数准备好之后发生,ngOnChnages
并且在组件为我们准备好之后被触发。所有初始化都可以在这个阶段发生,一个简单的示例是注入一个服务并在 init 上初始化它。
好的,我还分享一个示例代码供您查看,看看我们如何使用ngOnInit
并constructor
在下面的代码中:
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
@Component({
selector: 'my-app',
template: `<h1>App is running!</h1>
<my-app-main [data]=data></<my-app-main>`,
styles: ['h1 { font-weight: normal; }']
})
class ExampleComponent implements OnInit {
constructor(private router: Router) {} //Dependency injection in the constructor
// ngOnInit, get called after Component initialised!
ngOnInit() {
console.log('Component initialised!');
}
}
我将只添加在上面的解释中被跳过的一件重要的事情,并解释你什么时候必须使用ngOnInit
.
如果您通过例如ViewChildren、ContentChildren或ElementRef对组件的 DOM 进行任何操作,则您的本机元素在构造函数阶段将不可用。
但是,ngOnInit
一旦创建了组件并且ngOnChanges
调用了检查 ( ) 就会发生这种情况,此时您可以访问 DOM。
export class App implements OnInit, AfterViewInit, AfterContentInit {
@Input() myInput: string;
@ViewChild() myTemplate: TemplateRef<any>;
@ContentChild(ChildComponent) myComponent: ChildComponent;
constructor(private elementRef: ElementRef) {
// this.elementRef.nativeElement is undefined here
// this.myInput is undefined here
// this.myTemplate is undefined here
// this.myComponent is undefine here
}
ngOnInit() {
// this.elementRef.nativeElement can be used from here on
// value of this.myInput is passed from parent scope
// this.myTemplate and this.myComponent are still undefined
}
ngAfterContentInit() {
// this.myComponent now gets projected in and can be accessed
// this.myTemplate is still undefined
}
ngAfterViewInit() {
// this.myTemplate can be used now as well
}
}
第一个(构造函数)与类实例化有关,与Angular2无关。我的意思是构造函数可以用于任何类。您可以在其中对新创建的实例进行一些初始化处理。
第二个对应于 Angular2 组件的生命周期钩子:
引用自 angular 官方网站:
ngOnChanges
当输入或输出绑定值更改时调用ngOnInit
在第一个之后调用ngOnChanges
因此,ngOnInit
如果初始化处理依赖于组件的绑定(例如用 定义的组件参数@Input
),您应该使用,否则构造函数就足够了......
简短而简单的答案是,
Constructor
:constructor
是在构建组件时default method
运行(默认情况下)。当你创建an instance
一个类的时候也会constructor(default method)
被调用。所以换句话说,当组件被constructed or/and an instance is created constructor(default method)
调用并且相关代码被写入其中时被调用。基本上并且通常在 中Angular2
,它用于注入诸如services
在构建组件以供进一步使用时之类的东西。
OnInit
: ngOnInit 是组件的生命周期钩子,constructor(default method)
在组件初始化之后首先运行。
因此,将首先调用您的构造函数,然后在构造函数方法之后调用 Oninit。
引导.ts
import {Cmomponent, OnInit} from 'angular2/core';
import {ExternalService} from '../externalService';
export class app implements OnInit{
constructor(myService:ExternalService)
{
this.myService=myService;
}
ngOnInit(){
// this.myService.someMethod()
}
}
资源:生命周期钩子
你可以查看这个小演示,它显示了这两个东西的实现。
构造函数和构造函数之间的主要区别在于ngOnInit
生命周期钩子并在构造函数之后运行。组件插值模板和输入初始值在构造函数中不可用,但在.ngOnInit
ngOnInit
实际的区别是如何ngOnInit
影响代码的结构。大多数初始化代码都可以移到ngOnInit
-只要这不会产生竞争条件。
大量的初始化代码使得构造方法难以扩展、阅读和测试。
将初始化逻辑与类构造函数分离的常用方法是将其移至另一个方法,例如init
:
class Some {
constructor() {
this.init();
}
init() {...}
}
ngOnInit
可以在组件和指令中达到此目的:
constructor(
public foo: Foo,
/* verbose list of dependencies */
) {
// time-sensitive initialization code
this.bar = foo.getBar();
}
ngOnInit() {
// rest of initialization code
}
Angular 中类构造函数的主要作用是依赖注入。构造函数也用于 TypeScript 中的 DI 注释。几乎所有依赖项都作为属性分配给类实例。
平均组件/指令构造函数已经足够大了,因为它可以由于依赖关系而具有多行签名,将不必要的初始化逻辑放入构造函数主体有助于反模式。
异步初始化构造函数通常被认为是反模式并且有异味,因为类实例化在异步例程之前完成,这可能会产生竞争条件。如果不是这种情况,那么ngOnInit
其他生命周期钩子是更好的地方,特别是因为它们可以从async
语法中受益:
constructor(
public foo: Foo,
public errorHandler: ErrorHandler
) {}
async ngOnInit() {
try {
await this.foo.getBar();
await this.foo.getBazThatDependsOnBar();
} catch (err) {
this.errorHandler.handleError(err);
}
}
如果存在竞争条件(包括组件不应出现在初始化错误的情况),异步初始化例程应在组件实例化之前进行,并移至父组件、路由器保护等。
ngOnInit
比构造函数更灵活,并为单元测试提供了一些好处,这些好处在这个答案中有详细解释。
考虑到ngOnInit
在单元测试中不会在组件编译时自动调用,ngOnInit
因此可以在组件实例化后监视或模拟调用的方法。
在特殊情况下ngOnInit
可以完全存根来为其他组件单元提供隔离(例如,一些模板逻辑)。
子类只能扩充构造函数,不能替换它们。
由于this
不能在之前引用super()
,这对初始化优先级施加了限制。
考虑到 Angular 组件或指令用于ngOnInit
对时间不敏感的初始化逻辑,子类可以选择是否super.ngOnInit()
调用以及何时调用:
ngOnInit() {
this.someMethod();
super.ngOnInit();
}
单独使用构造函数是不可能实现的。
与许多其他语言一样,您可以在类级别、构造函数或方法中初始化变量。由开发人员决定在他们的特定情况下什么是最好的。但以下是决定时的最佳实践列表。
通常,您将在此处声明将在其余组件中使用的所有变量。如果值不依赖于其他任何东西,您可以初始化它们,或者如果它们不会改变,则使用 const 关键字创建常量。
export class TestClass{
let varA: string = "hello";
}
通常最好不要在构造函数中做任何事情,只将它用于将被注入的类。大多数情况下,您的构造函数应如下所示:
constructor(private http: Http, private customService: CustomService) {}
这将自动创建类级变量,因此您customService.myMethod()
无需手动操作即可访问。
NgOnit 是 Angular 2 框架提供的生命周期钩子。您的组件必须实现OnInit
才能使用它。这个生命周期钩子在构造函数被调用并且所有变量都被初始化之后被调用。你的大部分初始化应该在这里。您将确定 Angular 已正确初始化您的组件,并且您可以开始执行您需要的任何逻辑,OnInit
而不是在您的组件未正确完成加载时执行操作。
这是一张详细说明调用顺序的图像:
https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html
如果您使用的是 Angular 2 框架并且需要与某些生命周期事件进行交互,请使用框架为此提供的方法以避免出现问题。
为了测试这一点,我编写了这段代码,借鉴了NativeScript 教程:
用户.ts
export class User {
email: string;
password: string;
lastLogin: Date;
constructor(msg:string) {
this.email = "";
this.password = "";
this.lastLogin = new Date();
console.log("*** User class constructor " + msg + " ***");
}
Login() {
}
}
登录组件.ts
import {Component} from "@angular/core";
import {User} from "./../../shared/user/user"
@Component({
selector: "login-component",
templateUrl: "pages/login/login.html",
styleUrls: ["pages/login/login-common.css", "pages/login/login.css"]
})
export class LoginComponent {
user: User = new User("property"); // ONE
isLoggingIn:boolean;
constructor() {
this.user = new User("constructor"); // TWO
console.log("*** Login Component Constructor ***");
}
ngOnInit() {
this.user = new User("ngOnInit"); // THREE
this.user.Login();
this.isLoggingIn = true;
console.log("*** Login Component ngOnInit ***");
}
submit() {
alert("You’re using: " + this.user.email + " " + this.user.lastLogin);
}
toggleDisplay() {
this.isLoggingIn = !this.isLoggingIn;
}
}
控制台输出
JS: *** User class constructor property ***
JS: *** User class constructor constructor ***
JS: *** Login Component Constructor ***
JS: *** User class constructor ngOnInit ***
JS: *** Login Component ngOnInit ***
上面的答案并没有真正回答原始问题的这个方面:什么是生命周期钩子?我花了一段时间才明白这意味着什么,直到我这样想。
1)假设你的组件是一个人。人类的生活包括许多生活阶段,然后我们就结束了。
2)我们的人类组件可能有以下生命周期脚本:出生、婴儿、小学、青年、中年、老年、死亡、处置。
3)假设你想要一个创建孩子的功能。为了避免这变得复杂而幽默,你希望你的函数只在人类组件生命的年轻成人阶段被调用。因此,您开发的组件仅在父组件处于 Young Adult 阶段时才处于活动状态。Hooks 通过发出生命阶段的信号并让您的组件对其进行操作来帮助您做到这一点。
好玩的东西。如果你让你的想象力去实际编码这样的东西,它会变得复杂而有趣。
构造函数在 JavaScript 中是一个方法,在 es6 中被认为是类的一个特性。当类被实例化时,无论是否在 Angular 框架中使用,它都会立即运行构造函数。所以它被 JavaScript 引擎调用,而 Angular 没有对此进行控制。
import {Component} from '@angular/core';
@Component({})
class CONSTRUCTORTEST {
//This is called by Javascript not the Angular.
constructor(){
console.log("view constructor initialised");
}
}
“ConstructorTest”类在下面实例化;因此它在内部调用构造函数(所有这些都由 JavaScript(es6) 而非 Angular 发生)。
new CONSTRUCTORTEST();
这就是为什么 Angular 中存在ngOnInit生命周期钩子的原因。当 Angular 完成初始化组件时,ngOnInit 会渲染。
import {Component} from '@angular/core';
@Component({})
class NGONINITTEST implements onInit{
constructor(){}
//ngOnInit calls by Angular
ngOnInit(){
console.log("Testing ngOnInit");
}
}
首先,我们实例化如下的类,它发生在构造函数方法的立即运行中。
let instance = new NGONINITTEST();
ngOnInit 在必要时由 Angular 调用,如下所示:
instance.ngOnInit();
但你可能会问为什么我们在 Angular 中使用构造函数?
答案是依赖注入。正如前面提到的,当类被实例化时(在 Angular 调用 ngOnInit 之前),JavaScript 引擎会立即调用构造函数,因此 typescript 帮助我们获取在构造函数中定义的依赖项的类型,并最终告诉Angular 我们希望在该特定组件中使用什么类型的依赖项。
constructor()是组件生命周期中的默认方法,用于依赖注入。构造函数是一个打字稿功能。
ngOnInit()在构造函数之后调用,ngOnInit 在第一个 ngOnChanges 之后调用。
IE:
构造函数() -->ngOnChanges() -->ngOnInit()
如上所述,ngOnChanges()
当输入或输出绑定值更改时调用。
这里要注意两点:
两者都有不同的可用性。
构造函数: ES6 类(或本例中的 TypeScript)上的构造函数方法是类本身的特性,而不是 Angular 特性。当构造函数被调用时,它不在 Angular 的控制范围内,这意味着它不是一个合适的钩子来让你知道 Angular 何时完成了组件的初始化。JavaScript 引擎调用构造函数,而不是直接调用 Angular。这就是创建 ngOnInit(和 AngularJS 中的 $onInit)生命周期钩子的原因。考虑到这一点,有一个使用构造函数的合适场景。这是我们想要利用依赖注入的时候——本质上是为了将依赖“连接”到组件中。
由于构造函数是由 JavaScript 引擎初始化的,TypeScript 允许我们告诉 Angular 哪些依赖项需要映射到特定属性。
ngOnInit纯粹是为了给我们一个信号,表明 Angular 已经完成了组件的初始化。
此阶段包括针对我们可能绑定到组件本身的属性的更改检测的第一遍——例如使用 @Input() 装饰器。
因此,@Input() 属性在 ngOnInit 中可用,但是在构造函数中未定义,设计
构造函数是第一个执行的,它有时会在@input 数据为空时发生!所以我们使用 Constructor 来注入服务,然后 ngOnInit 发生。构造函数示例:
constructor(translate: TranslateService, private oauthService: OAuthService) {
translate.setDefaultLang('En');
translate.use('En');}
ngOnInit 的示例:
ngOnInit() {
this.items = [
{ label: 'A', icon: 'fa fa-home', routerLink: ['/'] },
{ label: 'B', icon: 'fa fa-home', routerLink: ['/'] }]
}
我认为 ngOnInit 就像 winForm 中的 InitialComponents() 。
两种方法都有不同的目标/职责。构造函数(这是一种语言支持的特性)的任务是确保表示不变式成立。否则通过向成员提供正确的值来确保实例有效。由开发人员决定“正确”的含义。
onInit() 方法(这是一个角度概念)的任务是允许对正确对象(表示不变)进行方法调用。反过来,每种方法都应确保在方法终止时保持表示不变式。
构造函数应该用于创建“正确”对象,onInit 方法使您有机会在定义明确的实例上调用方法调用。
构造函数在类被实例化时执行。它与角度无关。这是 Javascript 的特性,Angular 无法控制它
ngOnInit 是 Angular 特定的,当 Angular 使用其所有输入属性初始化组件时调用
@Input 属性在 ngOnInit 生命周期钩子下可用。这将帮助您执行一些初始化工作,例如从后端服务器获取数据等以显示在视图中
@Input 属性在构造函数中显示为未定义
当constructor
Angular “实例化/构造”组件时调用。该ngOnInit
方法是一个钩子,代表组件生命周期的初始化部分。一个好的做法是仅将其用于服务注入:
constructor(private
service1: Service1,
service2: Service2
){};
即使有可能,你也不应该在里面做一些“工作”。如果要启动必须在组件“初始化”时发生的某些操作,请使用ngOnInit
:
ngOnInit(){
service1.someWork();
};
此外,来自父组件的涉及输入属性的操作无法在构造函数中完成。它们应该放在ngOnInit
方法或其他钩子中。与视图相关的元素(DOM)也是如此,例如viewchild 元素:
@Input itemFromParent: string;
@ViewChild('childView') childView;
constructor(){
console.log(itemFromParent); // KO
// childView is undefined here
};
ngOnInit(){
console.log(itemFromParent); // OK
// childView is undefined here, you can manipulate here
};
Constructor
是 ES6 的一部分,打字稿也使用 es6 语法,现在也使用 es7,因此您可以利用打字稿编译为 es5/es4(根据您的定义)的高级功能来为旧浏览器提供支持。
WhilengOnInIt
是 Angular 的生命周期钩子。它在您的组件初始化时被初始化。(认为它的状态是任何新生命的诞生)
使用ngOnInIt
比较构造函数是明智的,因为您有另一个生命周期钩子,例如ngOnDestory
(将其视为任何生命的死亡)。在这里,您可以取消订阅任何可以防止任何内存泄漏的 observable。
如果有任何问题,请随时对此答案发表评论。
在 Angular 生命周期中
1) Angular 注入器检测构造函数参数并实例化类。
2)下一个角度调用生命周期
ngOnChanges --> 调用指令参数绑定。
ngOnInit --> 开始角度渲染...
调用具有角度生命周期状态的其他方法。
构造函数
每个类都有构造函数,构造函数不是特定于 Angular 的,而是从面向对象设计派生的概念。构造函数创建组件类的实例。
初始化
该ngOnInit
函数是 Angular 组件的生命周期方法之一。Angular 组件中的生命周期方法(或挂钩)允许您在组件生命周期的不同阶段运行一段代码。与构造函数方法不同,ngOnInit
方法来自OnInit
组件需要实现的 Angular 接口 ( ) 才能使用此方法。该ngOnInit
方法在组件创建后不久被调用。
constructor()可以接受参数并且可以用于依赖注入或constructor() 用于添加服务对象。
在 ngOnint() 之前调用构造函数;
ngOnInit()用于通过必要的服务函数调用来操作组件,或者通常,在 ngOnInit() 中而不是在构造函数中调用服务
constructor()
用于进行依赖注入。
ngOnInit()
等是ngOnChanges()
生命ngOnDestroy()
周期方法。ngOnChanges()
将是第一个被调用的,before ngOnInit()
,当绑定属性的值发生变化时,如果没有变化则不会被调用。ngOnDestroy()
在移除组件时调用。要使用它,OnDestroy
需要implement
由班级编辑。
我找到了答案,我试着把它翻译成英文:这个问题仍然出现,即使是在技术面试中。事实上,两者有很大的相似之处,但也存在一些差异。
构造函数是 ECMAScript 的一部分。另一方面 ngOnInit() 是角度的概念。
即使我们不使用 Angular,我们也可以在所有类中调用构造函数
LifeCycle:构造函数在ngOnInt()之前被调用
在构造函数中我们不能调用 HTML 元素。但是,在 ngOnInit() 中我们可以。
通常,服务的调用在 ngOnInit() 中而不是在构造函数中
Constructor是Typescript类提供的默认方法,专门用于初始化类成员,一般用于依赖注入服务,如上面的示例代码,或者定时器初始化,socket连接初始化
export class AppComponent {
title = 'angular-fork-join';
constructor(private http: HttpClient) {}
ngOnInit:是 Angular 提供的组件初始化时调用的生命周期钩子,专门用于业务逻辑、数据初始化、API 调用等,示例代码演示 API 调用:
export class HomeComponent implements OnInit {
products = [];
constructor(private dataService: DataService) { }
ngOnInit() {
this.dataService.sendGetRequest().subscribe((data: any[])=>{
console.log(data);
this.products = data;
})
}
}