关于angular:构造函数和ngOnInit之间的区别

Difference between Constructor and ngOnInit

默认情况下,Angular提供生命周期挂钩ngOnInit

如果我们已经有了constructor,为什么要使用ngOnInit


Constructor是类的默认方法,在类被实例化时执行,并确保类及其子类中字段的正确初始化。Angular或Better Dependency Injector(DI)分析构造函数参数,当它通过调用new MyClass()创建一个新实例时,它试图找到与构造函数参数类型匹配的提供程序,解析它们并像

1
new MyClass(someArg);

ngOnInit是一个生命周期钩子,由angular2调用,表示角已完成创建组件。

我们必须进口OnInit才能这样使用(实际执行OnInit不是强制性的,但被认为是良好的做法):

1
import {Component, OnInit} from '@angular/core';

然后,为了使用OnInit的方法,我们必须在类中这样实现。

1
2
3
4
5
6
7
8
9
export class App implements OnInit{
  constructor(){
     //called first time before the ngOnInit()
  }

  ngOnInit(){
     //called after the constructor and called  after the first ngOnChanges()
  }
}

Implement this interface to execute custom initialization logic after your directive's data-bound properties have been initialized.
ngOnInit is called right after the directive's data-bound properties have been checked for the first time,
and before any of its children have been checked.
It is invoked only once when the directive is instantiated.

大多数情况下,我们对所有初始化/声明都使用ngOnInit,避免在构造函数中工作。构造函数只能用于初始化类成员,但不应执行实际的"工作"。

因此,您应该使用constructor()来设置依赖注入,而不必使用太多其他方法。ngoninit()是更好的"开始"位置——它是解析组件绑定的位置/时间。

有关详细信息,请参阅以下内容:

  • https://angular.io/api/core/oninit网站

  • 角2分量构造器与OnInit


本文从多个角度探讨了建构主义与非建构主义在角度上的本质区别。这个答案提供了与组件初始化过程相关的最重要的差异解释,它也显示了不同的用法。

角度引导过程包括两个主要阶段:

  • 构造组件树
  • 运行变化检测

当Angular构造组件树时,调用组件的构造函数。所有生命周期挂钩都作为运行变更检测的一部分进行调用。

当Angular构造组件树时,根模块注入器已经配置好,因此可以注入任何全局依赖项。此外,当Angular实例化一个子组件类时,父组件的注入器也已经设置好,这样您就可以注入在父组件上定义的提供程序,包括父组件本身。组件构造函数是在注入器上下文中调用的唯一方法,因此,如果需要任何依赖项,则只有在该依赖项中才能获取这些依赖项。

当Angular开始更改检测时,将构造组件树,并调用树中所有组件的构造函数。此外,每个组件的模板节点都添加到DOM中。在变更检测期间,会处理@Input通信机制,因此您不能期望在构造函数中有可用的属性。它将在ngOnInit之后提供。

让我们看一个简单的例子。假设您有以下模板:

1
2
<my-app>
   <child-comp [i]='prop'>

所以Angular开始引导应用程序。正如我所说,它首先为每个组件创建类。所以它称为MyAppComponent构造函数。它还创建了一个dom节点,该节点是my-app组件的宿主元素。然后继续为child-comp创建一个主机元素,并调用ChildComponent构造函数。在这个阶段,它并不真正关心i输入绑定和任何生命周期挂钩。因此,当此过程完成时,角度将以以下组件视图树结束:

1
2
3
4
5
6
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,以显示在构造函数中可以执行的初始化逻辑类型。

以下几篇文章将帮助您更好地理解该主题:

  • 你需要知道的关于角度变化检测的一切
  • Angular的$Digest在新版Angular中重生
  • 属性绑定的机制以角度更新


我认为最好的例子是使用服务。假设我想在组件"激活"时从服务器获取数据。假设在从服务器获取数据后,我还想对数据做一些额外的事情,也许我会得到一个错误,并希望以不同的方式记录它。

在构造函数上使用ngoninit非常简单,它还限制了需要添加到应用程序中的回调层的数量。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
export class Users implements OnInit{

    user_list: Array;

    constructor(private _userService: UserService){
    };

    ngOnInit(){
        this.getUsers();
    };

    getUsers(){
        this._userService.getUsersFromService().subscribe(users =>  this.user_list = users);
    };


}

使用构造函数,我可以调用我的用户服务并填充我的用户列表,但也许我想用它做一些额外的事情。就像确保一切都是上层的情况一样,我不完全确定我的数据是如何通过的。

因此,使用Ngoninit更容易。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
export class Users implements OnInit{

    user_list: Array;

    constructor(private _userService: UserService){
    };

    ngOnInit(){
        this.getUsers();
    };

    getUsers(){
        this._userService.getUsersFromService().subscribe(users =>  this.user_list = users);
        this.user_list.toUpperCase();
    };


}

它使查看更容易,所以当初始化时,我只需要在组件中调用我的函数,而不必在其他地方挖掘它。实际上,它只是另一种工具,你可以用它来让将来更容易阅读和使用。另外,我发现将函数调用放在构造函数中是一种非常糟糕的做法!


好的,首先,ngOnInit是角生命周期的一部分,而constructor是es6 javascript类的一部分,所以主要的区别就从这里开始!…

看看下面我创建的图表,它显示了角的生命周期。

ngOnInit vs constructor

在Angular2+中,我们使用constructor为我们做DI(Dependency Injection),而在Angular1中,它是通过调用String方法并检查注入了哪个依赖项来实现的。

如上图所示,ngOnInit发生在构造函数准备就绪和ngOnChnages之后,并在组件准备就绪后被激发。所有初始化都可以在这个阶段进行,一个简单的示例正在注入一个服务,并在in it上对其进行缩写。

好的,我还分享了一个示例代码供您查看,看看我们如何在下面的代码中使用ngOnInitconstructor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';


@Component({
 selector: 'my-app',
 template: `App is running!
  <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!');
  }
}


第一个(构造函数)与类实例化相关,与angular2无关。我的意思是构造函数可以用于任何类。您可以对新创建的实例进行一些初始化处理。

第二个对应于Angular2组件的生命周期挂钩:

引用自安格尔官方网站:

  • ngOnChanges is called when an input or output binding value changes
  • ngOnInit is called after the first ngOnChanges

因此,如果初始化处理依赖于组件的绑定(例如,用@Input定义的组件参数),那么应该使用ngOnInit,否则构造函数就足够了…


简单而简短的答案是,

ConstructorConstructor是一个default method运行(由deafult)时,组件正在建设。创建类的an instance时,也会调用constructor(default method)。也就是说,当组件被调用时,调用constructed or/and an instance is created constructor(default method),并调用编写在其中的相关代码。基本上,一般在Angular2中,它用于在构建组件以供进一步使用时注入类似services的东西。

OnInit:ngoninit是组件的生命周期钩子,在初始化组件时,它首先在constructor(default method)之后运行。

因此,将首先调用构造函数,稍后在构造函数方法之后调用OnInit。

BoT.TS

1
2
3
4
5
6
7
8
9
10
11
12
13
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()
   }
}

资源:生命周期挂钩

您可以检查这个小的演示,它显示了这两个方面的实现。


(P)I will just add one important thing that was skipped in the explanations above and explains when you must use EDOCX1 nabel.(p)(P)6.If you are doing any manipulation of the component's dom via e.g.Viewchilden,contentchildren or elementref,your native elements will not be available during the constructor phase.(p)(P)然而,由于EDOCX1是一个英文字母,0是一个英文字母,11 the component has been created and the checks(EDOCX1 pensic 2)have been called you can access the dom at this point.(p)字母名称


为了测试这一点,我编写了这段代码,借鉴了NativeScript教程:

用户电报系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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() {
    }
}

登录.component.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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;
  }

}

控制台输出

1
2
3
4
5
JS: *** User class constructor property ***  
JS: *** User class constructor constructor ***  
JS: *** Login Component Constructor ***  
JS: *** User class constructor ngOnInit ***  
JS: *** Login Component ngOnInit ***

constructor和ngOnInit的主要区别在于ngOnInit是生命周期钩子,在constructor之后运行。组件内插模板和输入初始值在构造函数中不可用,但在ngOnInit中可用。

实际的区别是ngOnInit如何影响代码的结构。大多数初始化代码可以移动到ngOnInit—只要这不创建争用条件。

构造函数反模式

大量的初始化代码使得构造函数方法难以扩展、读取和测试。

将初始化逻辑与类构造函数分离的常用方法是将其移动到另一个方法,如init

1
2
3
4
5
6
7
class Some {
  constructor() {
    this.init();
  }

  init() {...}
}

ngOnInit可用于组件和指令:

1
2
3
4
5
6
7
8
9
10
11
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语法中获益:

1
2
3
4
5
6
7
8
9
10
11
12
13
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()之前引用,这就限制了初始化优先级。

考虑到角组件或指令使用ngOnInit作为时间不敏感的初始化逻辑,子类可以选择是否调用super.ngOnInit(),以及何时调用:

1
2
3
4
ngOnInit() {
  this.someMethod();
  super.ngOnInit();
}

这将不可能单独使用构造函数来实现。


与许多其他语言一样,您可以在类级别、构造函数或方法上初始化变量。在特定情况下,由开发人员决定什么是最好的。但下面列出了在决策时的最佳实践。

类级变量

通常,您将在这里声明将在其余组件中使用的所有变量。如果值不依赖于其他任何内容,则可以初始化它们;如果常量不变,则可以使用const关键字创建常量。

1
2
3
export class TestClass{
    let varA: string ="hello";
}

构造函数

通常,最好不要在构造函数中做任何事情,只在将要注入的类中使用它。大多数时候,您的构造函数应该如下所示:

1
   constructor(private http: Http, private customService: CustomService) {}

这将自动创建类级变量,因此您无需手动访问customService.myMethod()

恩贡尼特

NGONIT是由Angular2框架提供的生命周期挂钩。您的组件必须实现OnInit才能使用它。在调用构造函数并初始化所有变量之后,将调用此生命周期挂钩。您的大部分初始化应该放在这里。您可以确定Angular已经正确初始化了组件,并且可以开始在OnInit中执行任何需要的逻辑,而不是在组件未正确完成加载时执行任何操作。

下面是一张图片,详细说明了被称为:

enter image description here

https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html网站

TLDR

如果您使用的是Angular2框架,并且需要与某些生命周期事件交互,请使用框架为此提供的方法以避免出现问题。


上面的答案并不能真正回答原始问题的这个方面:什么是生命周期挂钩?我花了一段时间才明白这意味着什么,直到我这样想。

1)假设你的组件是人。人类的生命包括许多生命阶段,然后我们就会死亡。

2)我们的人类组件可以有以下生命周期脚本:出生、婴儿、小学、青年、中年、老年、死亡、处理。

3)假设您希望有一个函数来创建子对象。为了避免这变得复杂和幽默,你只希望你的功能被称为在青年成人阶段的人类组成部分的生活。因此,您开发的组件只有在父组件处于年轻成人阶段时才有效。钩子通过发出生命阶段的信号并让您的组件对其进行操作来帮助您做到这一点。

有趣的东西。如果你让你的想象力去真正地编码这样的东西,它会变得复杂和有趣。


这里需要注意两件事:

  • 每当创建该类的对象时,都会调用构造函数。
  • 创建组件后调用ngoninit。
  • 两者都有不同的可用性。


    构造器是javascript中的一种方法,在es6中被视为类的一个特性,当类被实例化时,无论它是否用于角度框架,它都会立即运行构造器,因此被javascript引擎调用,而angular对此没有控制。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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)实现的,没有角度)。

    1
    new CONSTRUCTORTEST();

    这就是为什么在angular.ngoninit中有ngoninit生命周期挂钩,当angular完成组件初始化后,它会呈现。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import {Component} from '@angular/core';
    @Component({})
    class NGONINITTEST implements onInit{
       constructor(){}
       //ngOnInit calls by Angular
       ngOnInit(){
         console.log("Testing ngOnInit");
       }
    }

    首先,我们将类实例化为下面的类,该类发生在构造函数方法的立即运行中。

    1
    let instance = new NGONINITTEST();

    如有必要,Ngoninit可用角形表示,如下所示:

    1
    instance.ngOnInit();

    但是你可能会问我们为什么在角度上使用构造器?

    答案是依赖项注入。正如前面提到的,当类被实例化时,javascript引擎立即调用构造函数(在用angular调用ngoninit之前),因此typescript帮助我们在构造函数中定义依赖项的类型,并最终告诉angular我们要在其中使用哪种类型的依赖项特定组件。


    constructor()是组件生命周期中的默认方法,用于依赖项注入。构造函数是一个类型脚本功能。

    在构造函数之后调用ngoninit(),在第一个ngonchange之后调用ngoninit。

    即constructor()->NgonChanges()->NgonInit())

    如上所述,当输入或输出绑定值更改时调用ngonchanges()。


    这两种方法都有不同的目标/责任。构造函数的任务(这是一个语言支持的特性)是确保表示不变量保持不变。否则,通过向成员提供正确的值来确保实例有效。由开发人员决定"正确"的含义。

    oninit()方法(角度概念)的任务是允许对正确的对象进行方法调用(表示不变量)。每个方法依次应确保在方法终止时表示不变。

    构造函数应该用于创建"正确"的对象,OnInit方法为您提供了在定义良好的实例上调用方法调用的机会。


    (P)建设者:The constructor method on an es6 class(or typescript in this case)is a feature of a class itself,rather than an corporal feature.当建筑商参与时,它就不在角控制之下,这意味着它不是一个可套用的Hook,让你知道角已经界定了初步的组成部分。Javascript engine calls the constructor,not crotal directly.Which is why the ngoniit(and$oninit in Angolarjs)lifecycle hook was created.如果您发现有错误,请尽管发表评论!这是当我们想利用依赖性移植――在组成中"Wiring Up"的必要性。(p)(P)As the constructor is initialised by the Javascript engine,and typescript allows us to tell oranger what dependencies we require to be mapped against a specific property.(p)(P)Ngoninit is purely there to give us a signal that cross has finished initialising the component.(p)(P)This phase includes the first pass at change detection against the properties that we may bind to the component itself-such as using an@input(……)designator.(p)(P)Due to this,the@input(……)properties are available inside NGONIIT,however are undefined inside the constructor,by design(p)


    构造函数是第一个,当@input data为空时有时会发生这种情况!所以我们使用构造函数来声明服务,然后发生ngoninit。控制器样品:

    1
    2
    3
     constructor(translate: TranslateService, private oauthService: OAuthService) {
        translate.setDefaultLang('En');
            translate.use('En');}

    OnInit样品:

    1
    2
    3
    4
    5
    ngOnInit() {
        this.items = [
          { label: 'A', icon: 'fa fa-home', routerLink: ['/'] },
          { label: 'B', icon: 'fa fa-home', routerLink: ['/'] }]
    }

    我认为OnInit就像WinForm中的InitialComponents()。


    我找到了答案,我试着把它翻译成英语:这个问题仍然存在,即使是在技术采访中。事实上,两者有很大的相似性,但也有一些差异。

    • 构造函数是ECMAScript的一部分。另一方面,ngoninit()是角度的概念。

    • 我们可以在所有类中调用构造函数,即使我们不使用angular

    • 生命周期:在ngonint()之前调用构造函数。

    • 在构造函数中,我们不能调用HTML元素。但是,在Ngoninit()我们可以。

    • 通常,在ngoninit()中调用服务,而不是在构造函数中调用服务

      来源:http://www.angular-tuto.com/angular/component diff


    在角生命周期中

    1)角度注入器检测构造函数参数并实例化类。

    2)下一个角度调用生命周期

    角度生命周期挂钩

    ngonchanges-->在指令参数绑定中调用。

    ngoninit->开始角度渲染…

    调用具有角生命周期状态的其他方法。


    当有角度的"建立/构造"组件时,调用constructorngOnInit方法是一个钩子,它表示组件生命周期的初始化部分。一个好的做法是只将其用于服务注入:

    1
    2
    3
    4
    constructor(private
        service1: Service1,
        service2: Service2
    ){};

    即使可能,你也不应该在里面做一些"工作"。如果要启动一些必须在组件"初始化"时发生的操作,请使用ngOnInit

    1
    2
    3
    ngOnInit(){
        service1.someWork();
    };

    此外,涉及来自父组件的输入属性的操作不能在构造函数中完成。它们应该放在ngOnInit方法或另一个钩子中。与视图(dom)相关的元素也是如此,例如,viewchild元素:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @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()用于依赖注入。

    ngOnInit()ngOnChanges()ngOnDestroy()等是生命周期方法。在ngOnInit()之前,第一个被调用的是ngOnChanges(),当绑定属性的值发生变化时,如果没有变化,就不会被调用。删除组件时调用ngOnDestroy()。要使用它,OnDestroy需要由类生成implement