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

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


当前回答

构造函数是第一个执行的,当@input data为空时有时会发生这种情况! 所以我们使用构造函数注入服务,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()。

其他回答

为了测试这一点,我从NativeScript教程中借鉴编写了以下代码:

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 ***  

构造函数()可以接受参数并用于依赖注入,构造函数()用于添加服务对象。

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

ngOnInit()用于用必要的服务函数调用操作组件,通常,ngOnInit()中调用服务,而不是在构造函数中调用服务

第一个(构造函数)与类实例化有关,与Angular2无关。构造函数可以用在任何类上。您可以在其中放入一些新创建实例的初始化处理。

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

引用自angular官方网站:

当输入或输出绑定值发生变化时调用ngOnChanges 在第一个ngOnChanges之后调用ngOnInit

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

《Angular中构造函数和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  

然后才运行更改检测和更新my-app的绑定,并在MyAppComponent类上调用ngOnInit。然后它继续更新child-comp的绑定,并在ChildComponent类上调用ngOnInit。

你可以在构造函数或ngOnInit中进行初始化逻辑,这取决于你需要什么。例如,本文介绍了如何在@ViewChild查询被求值之前获取ViewContainerRef,这篇文章展示了可以要求在构造函数中执行什么类型的初始化逻辑。

这里有一些文章可以帮助你更好地理解这个话题:

关于Angular中的变更检测,你需要知道的一切 Angular的$digest在Angular的新版本中重生了 属性绑定的机制会在Angular中更新

构造函数和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();
}

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