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

为什么要使用ngOnInit,如果我们已经有一个构造函数?


当前回答

构造函数和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和其他生命周期钩子是更好的地方,特别是因为它们可以从异步语法中受益:

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可以完全stub,为其他组件单元(例如,一些模板逻辑)提供隔离。

继承

子类只能扩充构造函数,不能替换它们。

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

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

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

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

其他回答

构造函数

每个类都有构造函数,构造函数不是Angular特有的,而是从面向对象设计中派生出来的概念。构造函数创建组件类的实例。

OnInit

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

像许多其他语言一样,可以在类级、构造函数或方法上初始化变量。这取决于开发人员来决定在他们的特定情况下什么是最好的。但下面是在做决定时的最佳实践清单。

类级变量

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

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

构造函数

通常,最好的做法是在构造函数中不做任何事情,只将它用于将要被注入的类。大多数时候你的构造函数应该是这样的:

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

这将自动创建类级变量,因此您将可以访问customService.myMethod(),而不必手动执行。

NgOnInit

NgOnit是Angular 2框架提供的一个生命周期钩子。你的组件必须实现OnInit才能使用它。这个生命周期钩子在构造函数被调用并初始化所有变量之后被调用。大部分初始化工作应该放在这里。你将确信Angular已经正确地初始化了你的组件,并且你可以开始在OnInit中执行任何你需要的逻辑,而不是在你的组件还没有正确加载完成的时候进行操作。

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

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

TLDR

如果你正在使用Angular 2框架,并且需要与特定的生命周期事件交互,请使用框架提供的方法来避免问题。

Constructor()用于进行依赖注入。

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

构造函数是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生命周期钩子。ngOnInit在Angular完成组件初始化后才会呈现。

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

首先,我们实例化如下所示的类,它发生在构造函数方法的立即运行中。

let instance = new NGONINITTEST();

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

instance.ngOnInit();

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

答案是依赖注入。正如前面提到的,当类被实例化时(在Angular调用ngOnInit之前),JavaScript引擎会立即调用构造函数,因此typescript帮助我们获得在构造函数中定义的依赖类型,并最终告诉Angular我们想在特定组件中使用什么类型的依赖。

我将添加一个重要的东西,在上面的解释中被跳过,并解释什么时候你必须使用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
  }
}