构造函数和 ngOnInit 之间的区别

Difference between Constructor and ngOnInit

Angular默认提供生命周期钩子ngOnInit

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

Constructor 是 class 的默认方法,在实例化 class 时执行,并确保 class 及其子字段中的字段正确初始化classes。 Angular 或更好的依赖注入器 (DI),分析构造函数参数,当它通过调用 new MyClass() 创建新实例时,它会尝试找到与构造函数参数类型匹配的提供程序,解析它们并传递他们像

这样的构造函数
new MyClass(someArg);

ngOnInit 是由 Angular 调用的生命周期挂钩,用于指示 Angular 已完成创建组件。

我们必须像这样导入 OnInit 才能使用它(实际上实施 OnInit 不是强制性的,但被认为是好的做法):

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

然后要使用方法 OnInit,我们必须像这样实现 class:

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.

大多数情况下我们对所有 initialization/declaration 使用 ngOnInit 并避免在构造函数中工作。构造函数应该只用于初始化 class 成员,而不应该做实际的“工作”。

所以你应该使用 constructor() 来设置依赖注入而不是其他。 ngOnInit() 是更好的“开始”位置 - 它 where/when 组件的绑定已解析。

有关更多信息,请参阅此处:

重要的是要注意 @Input 值在构造函数中不可访问(感谢@tim 在评论中提出建议)

简短的答案是,

Constructor : constructor 是一个 default method 运行(默认)时组件正在建设中。当您创建 class 的 an instance 时,也会调用 constructor(default method)。所以换句话说,当组件被调用时 constructed or/and an instance is created constructor(default method) 并在其中编写相关代码。基本上和通常在 Angular2 中,它用于在构建组件以供进一步使用时注入诸如 services 之类的东西。

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

因此,您的构造函数将首先被调用,Oninit 将在构造函数方法之后调用。

boot.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() 
   }
}

资源:LifeCycle hook

您可以查看此 small demo,它显示了这两种情况的实现。

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

在构造函数上使用 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,但也许我想用它做一些额外的事情。比如确保一切正常 upper_case,我不完全确定我的数据是如何通过的。

因此使用 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();
    };


}

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

第一个(构造函数)与class实例化有关,与Angular2无关。我的意思是构造函数可以用于任何 class。可以在里面放一些新创建的实例的初始化处理。

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

转自angular官网:

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

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

为了测试这一点,我编写了这段代码,借用了 NativeScript Tutorial:

user.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() {
    }
}

login.component.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) 我们的人类组件可能具有以下生命周期脚本:出生、婴儿、小学、青年、Mid-age 成人、老年、死亡、处置。

3) 假设您想要一个函数来创建 children。为了避免它变得复杂和相当幽默,您希望您的函数仅在人类组件生命的年轻成人阶段被调用。所以你开发了一个只有当 parent 组件处于 Young Adult 阶段时才活跃的组件。挂钩通过指示生命的那个阶段并让您的组件对其进行操作来帮助您做到这一点。

有趣的东西。如果你让你的想象力去实际编码这样的东西,它会变得复杂而有趣。

两种方法有不同goals/responsibilities。构造函数(这是一种语言支持的功能)的任务是确保表示不变量成立。否则声明通过为成员提供正确的值来确保实例有效。 'correct' 的含义由开发人员决定。

onInit() 方法(这是一个 angular 概念)的任务是允许在正确的对象上调用方法(表示不变)。每个方法都应依次确保表示不变量在方法终止时成立。

构造函数应该用于创建 'correct' 对象,onInit 方法使您有机会在定义明确的实例中调用方法。

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

Class 级别变量

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

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

构造函数

通常最好不要在构造函数中执行任何操作,而只将其用于将要注入的 classes。大多数时候你的构造函数应该是这样的:

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

这将自动创建 class 级变量,因此您无需手动操作即可访问 customService.myMethod()

NgOnInit

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

这是一张详细说明调用顺序的图片:

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

TLDR

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

构造函数是JavaScript中的一个方法,被认为是class中的一个特征es6 。当 class 被实例化时,它会立即运行构造函数,无论它是在 Angular 框架中使用还是 not.So 它是由 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"class;所以它内部调用了 构造函数(所有这些发生在 JavaScript(es6) no Angular)。

new CONSTRUCTORTEST();

这就是为什么 ngOnInit 生命周期挂钩在 Angular.ngOnInit 渲染时 Angular 完成初始化组件。

import {Component} from '@angular/core';
@Component({})
class NGONINITTEST implements onInit{
   constructor(){}
   //ngOnInit calls by Angular
   ngOnInit(){
     console.log("Testing ngOnInit");
   }
}

首先我们实例化class,如下所示,它会立即运行构造方法。

let instance = new NGONINITTEST();

ngOnInit 在必要时由 Angular 调用,如下所示:

instance.ngOnInit();

但是你可能会问为什么我们在 Angular 中使用构造函数?

答案是dependencies injections。如前所述,当class 被实例化(在 Angular 调用 ngOnInit 之前),所以 typescript 帮助我们获取在构造函数中定义的依赖项的类型,并最终告诉 Angular 我们要使用什么类型的依赖项在该特定组件中。

好的,首先ngOnInitAngular生命周期的一部分,而constructorES6 JavaScript class 的一部分,所以主要区别就从这里开始!...

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

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

正如您在上图中看到的,ngOnInit 在构造函数准备就绪后发生,ngOnChnages 并在组件为我们准备好后被触发。所有的初始化都可以在这个阶段发生,一个简单的例子是注入一个服务并在初始化时初始化它。

好的,我也分享一个示例代码给大家看看,看看我们如何在下面的代码中使用ngOnInitconstructor

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!');
  }
}

这里有两点要注意:

  1. 每当创建对象 class 时都会调用构造函数。
  2. 创建组件后调用 ngOnInit。

两者的可用性不同。

文章The essential difference between Constructor and ngOnInit in Angular从多个角度探讨了差异。这个答案提供了与组件初始化过程相关的最重要的差异解释,也显示了用法上的差异。

Angular bootstrap 过程包括两个主要阶段:

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

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

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

当 Angular 开始变更检测时,将构建组件树并调用树中所有组件的构造函数。每个组件的模板节点也被添加到 DOM。 @Input 通信机制在变更检测期间处理,因此您不能指望在构造函数中获得可用的属性。它将在 ngOnInit.

之后可用

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

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

因此 Angular 开始 bootstrapping 应用程序。正如我所说,它首先为每个组件创建 classes。所以它调用 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 class 上运行更改检测和更新绑定 my-app 并调用 ngOnInit。然后它继续更新 child-comp 的绑定并在 ChildComponent class.

上调用 ngOnInit

您可以在构造函数或 ngOnInit 中执行初始化逻辑,具体取决于您需要什么。例如,文章 Here is how to get ViewContainerRef before @ViewChild query is evaluated 显示了在构造函数中需要执行哪种类型的初始化逻辑。

这里有一些文章可以帮助您更好地理解主题:

constructor()用来做依赖注入

ngOnInit()ngOnChanges()ngOnDestroy()等都是生命周期方法。 ngOnChanges() 会最先被调用,在 ngOnInit() 之前,当绑定 属性 的值发生变化时,如果没有变化则不会被调用。 ngOnDestroy() 在移除组件时调用。要使用它,OnDestroy 需要由 class 编辑 implement

在 Angular 个生命周期中

1) Angular 注入器检测构造函数参数并实例化 class。

2) 接下来 angular 调用生命周期

Angular Lifecycle Hooks

ngOnChanges --> 调用指令参数绑定。

ngOnInit --> 开始angular渲染...

使用 angular 生命周期状态调用其他方法。

构造函数: ES6 class(或本例中的 TypeScript)上的构造函数方法是 class 本身的一个特性,而不是 Angular 特性。当构造函数被调用时,它不受 Angular 的控制,这意味着它不是一个合适的钩子来让你知道 Angular 何时完成了组件的初始化。 JavaScript 引擎调用构造函数,而不是 Angular 直接调用。这就是创建 ngOnInit(和 AngularJS 中的 $onInit)生命周期钩子的原因。牢记这一点,有一个适合使用构造函数的场景。这是我们想要利用依赖注入的时候——本质上是为了将依赖“连接”到组件中。

由于构造函数由 JavaScript 引擎初始化,并且 TypeScript 允许我们告诉 Angular 我们需要针对特定​​ 属性 映射哪些依赖项。

ngOnInit 纯粹是为了给我们一个信号,表明 Angular 已经完成了组件的初始化。

此阶段包括针对我们可能绑定到组件本身的属性的更改检测的第一遍 - 例如使用 @Input() 装饰器。

因此,@Input() 属性在 ngOnInit 中可用,但在构造函数中未定义,这是设计

我将只添加一件在上面的解释中被跳过的重要事情,并解释你什么时候必须使用ngOnInit

如果您正在通过例如对组件的 DOM 进行任何操作ViewChildrenContentChildrenElementRef,您的原生元素在构造函数阶段将不可用。

但是,由于 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
  }
}

constructor 和 ngOnInit 的主要区别在于 ngOnInitlifecycle hook 并且在 constructor 之后运行。组件插值模板和输入初始值在构造函数中不可用,但在 ngOnInit.

中可用

实际区别在于 ngOnInit 如何影响代码的结构。大多数初始化代码可以移动到 ngOnInit - 只要这不会产生竞争条件 .

构造函数反模式

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

将初始化逻辑与 class 构造函数分离的常用方法是将其移动到另一个方法,如 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 中 class 构造函数的主要作用是依赖注入。构造函数也用于 TypeScript 中的 DI 注释。几乎所有依赖项都作为属性分配给 class 实例。

平均 component/directive 构造函数已经足够大,因为由于依赖关系它可以具有多行签名,将不必要的初始化逻辑放在构造函数主体中有助于反模式。

异步初始化

异步初始化构造函数通常被认为是反模式并且有异味,因为 class 实例化在异步例程完成之前完成,这会产生竞争条件。如果不是这种情况,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 可以完全存根以提供其他组件单元的隔离(例如,一些模板逻辑)。

继承

子 classes 只能增加构造函数,不能替换它们。

由于 this 不能在 super() 之前被引用,这就限制了初始化优先级。

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

ngOnInit() {
  this.someMethod();
  super.ngOnInit();
}

单独使用构造函数是不可能实现的。

constructor()是Component生命周期中的默认方法,用于依赖注入。构造函数是 Typescript 的一个特性。

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

即:

构造函数() --> ngOnChanges() --> ngOnInit()

如上所述 ngOnChanges() 在输入或输出绑定值更改时调用。

构造函数最先执行,@input数据为null时有时会出现! 所以我们使用 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() 。

constructor 在 Angular "instanciates/constructs" 组件时被调用。 ngOnInit 方法是一个钩子,代表组件生命周期的初始化部分。 一个好的做法是仅将其用于 服务注入 :

constructor(private 
    service1: Service1,
    service2: Service2
){};

即使可以,也不应该在里面做一些"work"。 如果您想启动某些必须在组件 "initialization" 发生的操作,请使用 ngOnInit:

ngOnInit(){
    service1.someWork();
};

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

@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
};

我找到了答案并尝试将其翻译成英文: 即使在技术面试中,这个问题仍然存在。其实两者有很大的相似之处,但也有一些不同。

  • 构造函数是 ECMAScript 的一部分。另一方面,ngOnInit() 是 angular.

    的概念
  • 即使不使用Angular

    也可以调用所有类的构造函数
  • LifeCycle:在ngOnInt()之前调用构造函数

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

  • 服务的调用一般在ngOnInit()中而不是在构造函数中

    来源:http://www.angular-tuto.com/Angular/Component#Diff

Constructor是构建组件(或其他class)时执行的函数。

ngOnInit 是属于组件 life-cycle 方法组的函数,它们在我们组件的不同时刻执行(这就是为什么名称 life-cycle ).以下是所有这些的列表:

构造函数将在任何 life-cycle 函数之前执行。

构造函数

每个 class 都有构造函数,构造函数并不特定于 Angular,而是源自面向对象设计的概念。构造函数创建组件实例 class.

OnInit

ngOnInit 函数是 Angular 组件的生命周期方法之一。 Angular 组件中的生命周期方法(或挂钩)允许您 运行 一段代码处于组件生命周期的不同阶段。 与构造函数方法不同,ngOnInit 方法来自组件需要实现的 Angular 接口 (OnInit) 才能使用此方法。 ngOnInit 方法在创建组件后不久被调用。

构造函数在实例化class时执行。它与 angular 无关。这是Javascript的特性,Angular无法控制

ngOnInit 是 Angular 特定的,当 Angular 已使用其所有输入属性初始化组件时调用

@Input 属性在 ngOnInit 生命周期挂钩下可用。这将帮助您进行一些初始化工作,例如从后端服务器获取数据等以显示在视图中

@Input 属性在构造函数中显示为未定义

Constructor 是 ES6 的一部分,打字稿也使用 es6 语法,现在也使用 es7,因此您可以利用打字稿将编译为 es5/es4(根据您的定义)提供的高级功能支持旧浏览器。

ngOnInIt是angular的生命周期钩子。它在您的组件初始化时初始化。 (考虑它是任何新生命诞生的状态)

与构造函数相比,使用 ngOnInIt 是明智的,因为您有另一个生命周期挂钩,如 ngOnDestory(将其视为任何生命的死亡)。在这里您可以取消订阅任何可观察到的内容,这有助于防止任何内存泄漏。

如有任何问题,请随时对此答案发表评论。

constructor()可以接受参数,可以用于依赖注入或者constructor()用于添加服务对象。

在 ngOnint() 之前调用构造函数;

ngOnInit() 用于操作组件调用必要的服务函数或者一般在ngOnInit()中调用服务而不是在构造函数中调用

Constructor是Typescript默认提供的方法class,专门用来初始化class成员,一般用于依赖注入 服务,如上面的示例代码,或定时器初始化、套接字连接初始化

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;
    })  
  }

}