为什么 ngOnInit 会被调用两次?

86

我试图创建一个新的组件,ResultComponent,但是它的 ngOnInit() 方法被调用了两次,我不知道为什么会这样。在代码中,ResultComponent 继承了父组件 mcq-component@Input

以下是代码:

父组件(MCQComponent)

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

@Component({
    selector: 'mcq-component',
    template: `
        <div *ngIf = 'isQuestionView'>
            .....
        </div>
        <result-comp *ngIf = '!isQuestionView' [answers] = 'ansArray'><result-comp>
    `,
    styles: [
        `
            ....
        `
    ],
    providers: [AppService],
    directives: [SelectableDirective, ResultComponent]
})
export class MCQComponent implements OnInit{
      private ansArray:Array<any> = [];
    ....
    constructor(private appService: AppService){}
    ....
}

子组件(result-comp)

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

@Component({
    selector:'result-comp',
    template: `
        <h2>Result page:</h2>

    `
})
export class ResultComponent implements OnInit{
    @Input('answers') ans:Array<any>;

    ngOnInit(){
        console.log('Ans array: '+this.ans);
    }
}
当运行时,console.log 出现了两次,第一次它显示了正确的数组,但第二次却显示 undefined。我一直没有弄明白:为什么在ResultComponent中的ngOnInit会被调用两次?

5
当代码无法运行时,请不要使用代码剪辑功能。 - Günter Zöchbauer
抱歉我使用了代码片段功能,但实际上我无法使用{}功能插入代码。 - Sagar Ganesh
从来没有遇到过 {} 的任何问题。问题出在哪里? - Günter Zöchbauer
1
代码无法在代码区正确设置。:( 这就是问题所在。 - Sagar Ganesh
24个回答

55

为什么它叫两次

当前,如果在检测组件内容/视图子项变化期间发生错误,则会调用ngOnInit两次(在DynamicChangeDetector中可见)。这可能导致后续错误隐藏原始错误。

这些信息来自于这个GitHub问题


因此,看起来你的错误可能源于代码其他地方与此组件有关。


3
谢谢您的回应,实际上我只是控制了isQuestionView标志,就是这样。 - Sagar Ganesh
@Shree,你能解释一下吗?我也遇到了同样的问题... - Ravindra Thorat
@RavindraThorat 您好,这有几种可能性,例如:1. 错误的闭合标签(如<my-comp><my-comp>)2. 重复添加同一组件。 - Sagar Ganesh
谢谢您的快速回复,但在我的情况下什么都没有发生。我在这里创建了一个线程 https://stackoverflow.com/questions/54632686/component-constructor-is-getting-called-twice-with-angular-elements。我不确定为什么它不能正常工作。 - Ravindra Thorat
在我的情况下,我遇到了一个与我的JSON对象之间的错误映射有关的静默错误。修复此错误可以防止对ngOnInit()的双重调用。谢谢 :) - Louis
ngOnInit在我重新加载页面时被调用两次。你能帮我解决一下吗? - rahul tiwari

27

这种情况发生在我身上是因为组件html存在缺陷。我忘记在宿主组件中关闭选择器标签。所以我有了这个<search><search>,而不是<search></search>——注意语法错误。

所以,根据@dylan的答案,检查您的组件html结构及其父级结构。


您真是个英雄。我已经盯着这个问题一个小时了,原来它就是我的问题所在。 - NewZeroRiot

23

在我的情况下,问题出在我引导子组件的方式上。在我的@NgModule装饰器元数据对象中,我在bootstrap属性中传递了“子组件”以及“父组件”。将子组件传递到bootstrap属性中会重置我的子组件属性并导致OnInit()被触发两次。

    @NgModule({
        imports: [ BrowserModule,FormsModule ], // to use two-way data binding ‘FormsModule’
        declarations: [ parentComponent,Child1,Child2], //all components
        //bootstrap: [parentComponent,Child1,Child2] // will lead to errors in binding Inputs in Child components
        bootstrap: [parentComponent] //use parent components only
    })

21

如果你在 app.module.ts 文件中使用了 platformBrowserDynamic().bootstrapModule(AppModule);,请将其注释掉并尝试。我曾经也遇到过同样的问题,我认为这可以帮助解决。


20
这可能是因为您将AppComponent设置为基本路由。
RouterModule.forRoot([
    { path: '', component: AppComponent }  // remove it
])

注意:AppComponentangular中默认被调用,因此无需再次调用,如下所示:

@NgModule({
  declarations: [
    AppComponent,
  ],
  bootstrap: [AppComponent] // bootstraping here by default
})
export class AppModule { }

这是我的情况。 - DongBin Kim

14

将这个放在这里以防万一有人来到这里。如果您的浏览器默认按钮类型是“提交”,例如下面的例子,NgOnInit也可能会被调用两次,在Chrome中 NextComponent 的 NgOnInit 将被调用两次:

<button class="btn btn-primary" (click)="navigateToNextComponent()">
为了修复它,请设置type属性:
<button class="btn btn-primary" type="button" (click)="navigateToNextComponent()">

2
谢谢,那正是我的问题所在。我猜这只适用于你的按钮是 <form> 的一部分的情况。 - omoser

14

ngOnInit() 钩子函数仅在所有指令实例化后运行一次。如果你在ngOnInit()中有订阅且没有取消订阅,那么如果订阅的数据发生更改,它将再次运行。

以下是一个 Stackblitz 示例,其中我在ngOnInit()中有一个订阅并将结果打印到控制台。由于加载一次并且数据发生更改再次加载,因此它会打印两次。

Stackblitz


6
不是没有输出,它只被记录了一次。输出结果是AngularAngular8pp只被记录了一次。 - codenamezero

5

每当出现任何模板错误时,就会发生这种情况。

在我的情况下,我使用了错误的模板引用,纠正后修复了我的问题。


2
你能详细说明一下“模板错误”吗?可以举个例子吗? - Jacques

4

在我的情况下,构建生成了每个文件 main-es5.js 和 main-es2015.js 的副本,删除所有 *-es5.js 的副本,这样就可以解决问题。

如果您也遇到同样的问题,请不要删除这些重复文件,只需添加这些属性即可。

<script src="elements-es5.js" nomodule defer>
<script src="elements-es2015.js" type="module">

4
如果您有多个路由器出口,可能会发生这种情况,如下所示:
<ng-container *ngIf="condition">
  <div class="something">
    <router-outlet></router-outlet>
  </div>
</ng-container>
<ng-container *ngIf="!condition">
  <div class="something-else">
    <router-outlet></router-outlet>
  </div>
</ng-container>

请注意,如果您这样做,它也可能会调用构造函数两次。构造函数被调用两次的事实表明该组件被创建了两次,当组件位于条件从 true 到 false 再到 true 的 *ngIf 中时,正是发生这种情况。
调试此问题的有用方法是创建一个名为 rnd 的类变量,如下所示:
rnd = Math.random()

在构造函数和ngOnInit中使用console.log输出它。如果值发生更改,则在第二次调用时,您就知道这是组件的新实例,并且不是两次调用相同的实例。虽然这不会解决您的问题,但可能表明存在问题。


条件式 <router-outlet *ngIf="<condition>"></router-outlet> 是我的问题,谢谢。 - Mohammad Zaid Pathan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接