为什么 ngOnInit 被调用了两次?

我尝试创建新的组件,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被调用了两次?

83333 次浏览

为什么叫两次

现在,如果在检测组件的内容/视图子级更改时发生错误,ngOnInit 将被调用两次(见 DynamicChangeDetector)。 这可能导致后续错误,从而隐藏原始错误。

这个信息来自这个 Github 的问题


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

这种情况发生在我身上是因为一个错误的组件 html。我忘记关闭主机组件中的选择器标记。所以我用这个 <search><search>代替了 <search></search>-注意语法错误。

因此,关于@dylan answer,请检查您的组件 html 结构及其父元素的结构。

在我的案例中,问题在于我是如何引导子组件的。 在我的 @ NgModule装饰器的元数据对象中,我在 引导属性中传递“ 子组件”和“ 父组件”。 在引导属性中传递子组件是 重置我的子组件 物业,并使 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
})

把这个放在这里,以防有人来这里。如果你的浏览器默认的按钮类型是“提交”,NgOnInit 也可以被调用两次,比如说如果你有以下命令,那么 NextComponent 的 NgOnInit 在 Chrome 中会被调用两次:

<button class="btn btn-primary" (click)="navigateToNextComponent()">

要修复它,设置类型:

<button class="btn btn-primary" type="button" (click)="navigateToNextComponent()">

只要有任何模板错误,就会发生这种情况。

在我的案例中,我使用了一个错误的模板引用并修正了这个问题。

如果你在 app.module.ts 中使用 platformBrowserDynamic().bootstrapModule(AppModule);,注释它并尝试。我也有同样的问题。我认为这有帮助

在我的例子中,当组件同时实现 改变OnInit时就会发生这种情况。尝试删除其中一个类。您也可以使用 NgAfterViewInit方法,它是在视图初始化后触发的,这样就保证它只被调用一次。

这可能是因为您将 AppComponent设置为基本路由

RouterModule.forRoot([
{ path: '', component: AppComponent }  // remove it
])

注意: AppComponentangular中是默认调用的,所以不需要调用它,如下所示:

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

这种情况发生在我身上,因为我有一个未命名的 <router-outlet>内的 *ngFor。它为循环中的每次迭代加载。

在这种情况下,解决方案是为每个插座使用唯一的名称,或者确保 DOM 中一次只有一个插座(可能是 w/an *ngIf)。

我自己也遇到过类似的问题,我呼叫一个组件,然后通过路由器插座引用同一个组件。

<layout>
<router-outlet></router-outlet>
</layout>

出口路线也指向布局。

在实例化所有指令之后,ngOnInit()挂钩只执行一次。如果您在 ngOnInit()内部有订阅,并且没有取消订阅,那么如果订阅的数据发生变化,它将再次运行。

在下面的 Stackblitz 示例中,我在 ngOnInit()中订阅并控制结果。它控制台两次,因为它加载一次,数据发生变化,然后再次加载。

Stackblitz

对我来说,发生这种情况是因为我在 Route 中注册了两次相同的组件:

{
path: 'meal-services',
children: [
{
path: '',
component: InitTwiceComponent,
canActivate: [AppVendorOpsGuard],
data: { roleWhitelist: [UserRoles.Manage] }
},
{
path: ':itemID',
component: InitTwiceComponent,
canActivate: [AppVendorOpsGuard],
data: { roleWhitelist: [UserRoles.Manage] }
}]
},
}

在我的例子中,我没有退订 ngOnInit 中的所有订阅。我只是从 毁灭内的所有订阅 没有订阅和这解决了我的问题。

这在子组件中发生在我身上。子组件将使用 * ngIf 显示何时满足某个条件。我的解决方案是 用 ngClass 替换 ngIf

超文本标示语言(HTML) :

<component [ngClass]="isLoading?'hide':'show'>"

在 CSS 中:

.show{ display: block; }


.hide{ display: none; }

在我的例子中,是一个 html 错误导致了这种情况 在 app.Component. html 中,我有类似于

<icpm-la-test><icpm-la-test>

而不是

<icpm-la-test></icpm-la-test>

有两个开始标签而不是结束标签。而且我在控制台上没有错误,功能运行良好,除了由于 ngOnInit 被调用两次导致订阅发生多个 api 调用之外。

在我的例子中,构建生成的每个文件 main-es5. js 和 main-es2015. js 的副本,删除所有 *-es5.js 副本,就可以了。

如果这也是你的情况,不要删除副本,只是添加这些属性

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

这可能表明您错误地启动了组件两次,原因可能有以下几点:

  1. 您已经将 AppRoutingModule导入了多个模块。通过你的应用程序搜索它,并确保你只有它包括在 AppModule

  2. 您已经将 AppModule导入到另一个模块中。这也将按照点1复制您的 AppRoutingModule

  3. 您的标记中有多个 <router-outlet>。这可能会让人感到困惑,因为如果您有多个嵌套路由,那么您实际上将需要不止一个 <router-outlet>。但是,您需要检查每个具有子路由的路由只有1个 <router-outlet>。有时候,复制父节点中的每个嵌套组件来查看实际的输出结果会更加容易。然后您可以看到,如果您错误地结束了额外的 <router-outlet>

  4. 您的包装器组件在 AppRoutingModule 中和子组件中指定。通常我会有一个包装器组件来处理标准的布局,比如页眉、页脚、菜单等等。所有我的子路线,然后将坐在这里,当设置在我的 AppRoutingModule。有时也可以在另一个组件中使用这个包装器组件,从而复制它,因为它已经在 AppRoutingModule中指定了。这将导致额外的 <router-outlet>

我把 XComponent放进去了

Bootstrap: [ AppComponent,XComponent]

应用程序模块

删除 XComponent ngOnInit 后只触发了一次。

如果你有多个这样的路由器插座,就会出现这种情况:

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

注意,如果这样做,它也可能最终调用构造函数两次。构造函数被调用两次的事实证明了组件被创建了两次,当一个组件位于 *ngIf中,其条件从 true 切换到 false 切换到 true 时,就会发生这种情况

对此进行调试的一个有用的方法是创建一个名为 rnd 的类变量,如下所示:

rnd = Math.random()

在构造函数和 ngOnInit 中记录它。如果值发生了变化,那么在第二次调用时您就会知道这是组件的一个新实例,而不是被调用两次的同一个实例。虽然这不会解决你的问题,但它可能是问题的指示。

当我在2021年偶然发现这个问题时,我刚刚从一个古老的 ng4迁移到一个更近的 ng10。 然而,这个应用程序是通过 index.jsp 集成在一个单一的 Java 后端的,我没有正确地迁移它。
如果您处于类似的位置,请检查您的 <script>标签。*-es2015.js 源应该有一个 type="module",而 *-es5.js 应该有一个 nomodule defer集,即:

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

这种情况发生在我身上是因为我在锚标记中使用了属性 href = “ #”。不要在锚标签中使用。如果您按照下面所示的角度代码使用 routerLink,并尝试避免在 appComponent. ts 中使用 ngInit (0方法,那么这个问题就会得到解决

<a  class="nav-link" [routerLink]="['/login']"> Sign In. </a>

我的解决方案是在组件中使用 ngIf来防止未定义的输入,在您的情况下将是:

<result-comp *ngIf="answers" [answers]="answers"></result-comp>

在我的案例中,问题是在点击“登录”按钮后登录页面被调用了两次。原因是“登出”按钮的语法不正确。导致问题的代码是:

<a routerLink="/login">
<i class="fa fa-sign-out" (click)="logout()"></i>
</a>

从图标标记中移除单击事件并将其放入链接标记中解决了这个问题。

<a routerLink="/login" (click)="logout()">
<i class="fa fa-sign-out"></i>
</a>