首页 文章

构造函数和ngOnInit之间的区别

提问于
浏览
643

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

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

20 回答

  • 0

    Constructor 是在实例化类时执行的类的默认方法,并确保在类及其子类中正确初始化字段 . Angular或更好的Dependency Injector(DI)分析构造函数参数,当它通过调用 new MyClass() 创建新实例时,它会尝试查找与构造函数参数类型匹配的提供程序,解析它们并将它们传递给构造函数,如

    new MyClass(someArg);
    

    ngOnInit 是由Angular2调用的生命周期钩子,表示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 进行所有初始化/声明,并避免在构造函数中工作 . 构造函数应仅用于初始化类成员,但不应该用于实际的"work" .

    所以你应该使用 constructor() 来设置依赖注入,而不是其他 . ngOnInit()是"start"更好的地方 - 它解析了's where/when components'绑定 .

    有关更多信息,请参阅:

  • 0

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

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

    这使得它更容易看到,因此我在初始化时只需在我的组件中调用我的函数,而不是在其他地方挖掘它 . 实际上它只是您可以使用的另一种工具,以便将来更容易阅读和使用 . 另外我发现在构造函数中放置函数调用真的很糟糕!

  • 12

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

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

    • 构建组件树

    • 运行变化检测

    当Angular构造组件树时,将调用组件的构造函数 . 所有生命周期挂钩都被称为运行更改检测的一部分 .

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

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

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

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

    所以Angular开始引导应用程序 . 正如我所说,它首先为每个组件创建类 . 所以它调用 MyAppComponent 构造函数 . 它还创建了一个DOM节点,它是 my-app 组件的主机元素 . 然后继续为 child-comp 创建一个host元素并调用 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 中执行初始化逻辑,具体取决于您需要的内容 . 例如,文章Here is how to get ViewContainerRef before @ViewChild query is evaluated显示了在构造函数中可能需要执行哪种类型的初始化逻辑 .

    以下是一些有助于您更好地理解该主题的文章:

  • 43

    OK, 首先 ngOnInitAngular lifecycle 的一部分,而 constructorES6 JavaScript类的一部分,所以主要区别从这里开始!...

    请看下面我创建的图表,其中显示了Angular的生命周期 .

    ngOnInit vs constructor

    在Angular2中,我们使用 constructor 为我们执行 DI(Dependency Injection) ,而在Angular 1中,它通过调用String方法并检查注入了哪个依赖项来实现 .

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

    好的,我也分享了一个示例代码供您查看,看看我们如何在下面的代码中使用 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!');
      }
    }
    
  • 0

    第一个(构造函数)与类实例化相关,与Angular2无关 . 我的意思是构造函数可以在任何类上使用 . 您可以为新创建的实例添加一些初始化处理 .

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

    引自官方角度的网站:

    输入或输出绑定值更改时调用ngOnChanges在第一次ngOnChanges之后调用ngOnInit

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

  • 76

    答案简短而简单,

    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 .

    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 ,其中显示了两者的实现 .

  • 11

    我将添加一个在上面的解释中跳过的重要事项,并解释当你 MUST 使用 ngOnInit 时 .

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

    但是,由于 ngOnInit 一旦创建了组件并且已经调用了检查( ngOnChanges ),就可以在此时访问DOM .

    export class App implements OnInit {
      @ViewChild('myTemplate') myTemplate: TemplateRef<any>;
    
      constructor(private elementRef: ElementRef) {
         // this.elementRef.nativeElement is undefined here
         // this.myTemplate is undefined here
      }
    
      ngOnInit() {
         // this.elementRef.nativeElement can be used from here on
         // this.myTemplate can be used from here on
      }
    }
    
  • 7

    为了测试这个,我写了这段代码,借用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;
      }
    
    }
    

    Console output

    JS: *** User class constructor property ***  
    JS: *** User class constructor constructor ***  
    JS: *** Login Component Constructor ***  
    JS: *** User class constructor ngOnInit ***  
    JS: *** Login Component ngOnInit ***
    
  • 15

    构造函数和 ngOnInit 之间的主要区别在于 ngOnInitlifecycle hook并在构造函数之后运行 . 组件插值模板和输入初始值在构造函数中不可用,但它们在 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 比构造函数更灵活,并为单元测试提供了一些好处,详见this answer .

    考虑到单元测试中的组件编译不会自动调用 ngOnInit ,在组件实例化后可以对 ngOnInit 中调用的方法进行 Spy 或模拟 .

    在特殊情况下, ngOnInit 可以完全存根,以便为其他组件单元提供隔离(例如,某些模板逻辑) .

    继承

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

    由于 thissuper() 之前无法引用,因此会对初始化优先级进行限制 .

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

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

    单独使用构造函数无法实现这一点 .

  • 19

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

    类级变量

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

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

    构造函数

    通常,最佳做法是不在构造函数中执行任何操作,只将其用于将要注入的类 . 大多数情况下,构造函数应如下所示:

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

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

    NgOnInit

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

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

    enter image description here

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

    TLDR

    如果您正在使用Angular 2框架并且需要与某些生命周期事件进行交互,请使用框架提供的方法来避免问题 .

  • 718

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

    1)说你的组件是一个人 . 人类的生活包括许多生活阶段,然后我们就会过期 .

    2)我们的人体成分可以具有以下生命周期脚本:出生,婴儿,小学,青年成人,中年成人,高级成人,死亡,处置 .

    3)假设你想要一个创造孩子的功能 . 为了防止这种情况变得复杂,而且相当幽默,你希望你的功能只能在人体成分生命的青年成人阶段被调用 . 因此,您开发的组件仅在父组件处于Young Adult阶段时才处于活动状态 . 钩子通过发出生命阶段的信号并让你的组件对其起作用来帮助你做到这一点 .

    好玩的东西 . 如果你让你的想象力去实际编写这样的东西,它会变得复杂,有趣 .

  • 13

    Angular 2构造函数: -

    • 构造函数是在构造组件时运行的默认方法 .

    • 构造函数是一个打字稿特征,它仅用于类实例化,而与Angular 2无关 .

    • 构造函数在ngOnInit()之前第一次调用 .

    Angular 2 ngOnInit: -

    • ngOnInit事件是一个Angular 2生命周期事件方法,在第一个ngOnChanges之后调用,而ngOnInit方法用于使用@Input定义的参数,否则构造函数是OK .

    • 在第一次ngOnChanges之后调用构造函数和ngOnInit之后调用ngOnInit .

    • 当输入或输出绑定值更改时,将调用ngOnChanges .

    下图解释了Angular的谎言循环 . 从那里你可以了解每个人被调用的顺序 .

    enter image description here

  • 24

    constructor 是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中使用构造函数?

    答案是 dependencies injections . 如前所述,构造函数在实例化类时(在通过Angular调用ngOnInit之前)立即通过JavaScript引擎调用,因此typescript帮助我们获取构造函数中定义的依赖项的类型,最后告诉Angular我们想要在该特定组件中使用哪种类型的依赖项 .

  • 11

    这里要注意两件事:

    • 只要为该类创建了一个对象,就会调用构造函数 .
      创建组件后调用
    • ngOnInit .

    两者都有不同的可用性 .

  • 4

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

    onInit()方法(角度概念)的任务是允许对正确的对象(表示不变)进行方法调用 . 每个方法应该确保在方法终止时表示不变量成立 .

    构造函数应该用于创建“正确”的对象,onInit方法使您有机会在定义良好的实例上调用方法调用 .

  • 78

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

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

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

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

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

  • 4

    constructor() 是组件生命周期中的默认方法,用于依赖项注入 . 构造函数是一个Typescript特征 .

    在构造函数之后调用 ngOnInit() ,并在第一次ngOnChanges之后调用ngOnInit .

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

    如上所述,当输入或输出绑定值发生变化时,将调用ngOnChanges() .

  • 45

    constructor() 用于执行依赖注入 .

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

  • 7

    在角度生命周期中

    1)角度注入器检测构造函数参数('s)并实例化类 .

    2)下一个角度呼叫生命周期

    Angular Lifecycle Hooks

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

    ngOnInit - >开始角度渲染......

    用角度生命周期状态调用其他方法 .

  • 4

    构造函数是第一个,当@input数据为空时有时会发生!所以我们使用Constructor进行声明服务,然后使用ngOnInit . 控制人员的例子:

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

    onInit的示例:

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

    我认为onInit就像winForm中的InitialComponents() .

相关问题