为什么 ngOnInit 被调用两次?
Why is ngOnInit called twice?
我正在尝试创建新组件 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
被调用了两次?
为什么会被调用两次
Right now, if an error happens during detecting changes of content/view children of a component, ngOnInit will be called twice (seen in DynamicChangeDetector).
This can lead to follow up errors that hide the original error.
此信息来源于此github issue
看来您的错误可能起源于您代码中与此组件相关的其他地方。
由于组件故障 html,我遇到了这种情况。我忘记关闭主机组件中的选择器标签。所以我有这个 <search><search>
,而不是 <search></search>
- 注意语法错误。
与@dylan 的回答如此相关,请检查您的组件 html 结构及其父组件的结构。
我的问题是我引导子组件的方式。
在我的 @NgModule 装饰器的元数据对象中,我在 bootstap 属性 以及“父组件”。
在 bootstap 属性 中传递子组件是 重置 我的子组件 properties 并使 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
})
把这个放在这里以防有人在这里结束。如果你的浏览器的默认按钮类型是 "submit",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);
,请评论并尝试。我有同样的问题。我认为这有帮助
在我的例子中,当 Component 同时实现 OnChanges 和 OnInit 时,就会发生这种情况。尝试删除其中一个 类。也可以使用ngAfterViewInit方法,在视图初始化后触发,保证调用一次。
这可能是因为您将 AppComponent
设置为基本路线
RouterModule.forRoot([
{ path: '', component: AppComponent } // remove it
])
注意: AppComponent
在 angular
中默认调用,因此不需要调用它,如下所示:
@NgModule({
declarations: [
AppComponent,
],
bootstrap: [AppComponent] // bootstraping here by default
})
export class AppModule { }
这发生在我身上,因为我在 *ngFor
中有未命名的 <router-outlet>
。它为循环中的每次迭代加载。
在这种情况下,解决方案是为每个插座取一个唯一的名称,或者确保 DOM 中一次只有一个(也许带有 *ngIf
).
我自己也有类似的问题,我正在调用一个组件,然后通过路由器插座引用相同的组件。
<layout>
<router-outlet></router-outlet>
</layout>
而且出口路线也指向布局。
ngOnInit()
仅在所有指令实例化后挂钩一次。如果您在 ngOnInit()
内有订阅并且没有取消订阅,那么如果订阅的数据发生变化,它将再次 运行。
在下面的 Stackblitz 示例中,我在 ngOnInit()
内订阅并控制结果。它控制台两次,因为它加载一次并且数据更改并再次加载。
对我来说,这是因为我在 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 中的所有订阅。我只是 取消订阅 ngOnDestroy 中的所有订阅,这解决了我的问题。
这发生在我的子组件中。子组件将使用 *ngIf 显示何时满足特定条件。我的解决方案是 将 ngIf 替换为 ngClass。
在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">
这可能表明您错误地启动了该组件两次。这可能有以下几个原因:
您已将 AppRoutingModule
导入到超过 1 个模块中。在您的应用中搜索它并确保您只将它包含在 AppModule
.
中
您已将 AppModule
导入另一个模块。根据第 1 点,这也会复制您的 AppRoutingModule
。
您的标记中有多个 <router-outlet>
。这个可能会让人困惑,因为如果你有多个嵌套路由,你实际上需要不止一个 <router-outlet>
。但是,您需要检查每条具有子路由的路由是否只有 1 <router-outlet>
。有时将每个嵌套组件复制到父组件中只是为了查看实际输出会更容易。然后你可以看看你是否错误地多了一个<router-outlet>
。
您的包装器组件在 AppRoutingModule 和子组件内指定。通常我会有一个包装器组件来处理标准布局,如页眉、页脚、菜单等。然后,当我的 AppRoutingModule
中设置时,我的所有子路由都将位于其中。有时也可以在另一个组件中使用这个包装器组件,从而复制它,因为它已经在 AppRoutingModule
中指定。这将导致额外的 <router-outlet>
.
我将 XComponent 放在了
bootstrap: [AppComponent, XComponent]
在app.module.ts.
删除 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>
请注意,如果您这样做,它也可能最终调用构造函数两次。构造函数被调用两次的事实证明组件被创建了两次,这正是当组件位于条件从 true 到 false 到 true
的 *ngIf
中时发生的情况
一个有用的调试方法是创建一个名为 rnd 的 class 变量,如下所示:
rnd = Math.random()
and console.log 它在构造函数和 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>
这发生在我身上,因为我在锚标记中有 arribute href="#"。不要在锚标签中使用。如果您按照 angular 代码使用 routerLink,这将得到修复,如下所示,并尽量避免在 appComponent.ts
中使用 ngInit(0 方法
<a class="nav-link" [routerLink]="['/login']"> Sign In. </a>
我的解决方案是在组件中使用 ngIf
来防止未定义的输入,在你的情况下是:
<result-comp *ngIf="answers" [answers]="answers"></result-comp>
我正在尝试创建新组件 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
被调用了两次?
为什么会被调用两次
Right now, if an error happens during detecting changes of content/view children of a component, ngOnInit will be called twice (seen in DynamicChangeDetector). This can lead to follow up errors that hide the original error.
此信息来源于此github issue
看来您的错误可能起源于您代码中与此组件相关的其他地方。
由于组件故障 html,我遇到了这种情况。我忘记关闭主机组件中的选择器标签。所以我有这个 <search><search>
,而不是 <search></search>
- 注意语法错误。
与@dylan 的回答如此相关,请检查您的组件 html 结构及其父组件的结构。
我的问题是我引导子组件的方式。 在我的 @NgModule 装饰器的元数据对象中,我在 bootstap 属性 以及“父组件”。 在 bootstap 属性 中传递子组件是 重置 我的子组件 properties 并使 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
})
把这个放在这里以防有人在这里结束。如果你的浏览器的默认按钮类型是 "submit",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);
,请评论并尝试。我有同样的问题。我认为这有帮助
在我的例子中,当 Component 同时实现 OnChanges 和 OnInit 时,就会发生这种情况。尝试删除其中一个 类。也可以使用ngAfterViewInit方法,在视图初始化后触发,保证调用一次。
这可能是因为您将 AppComponent
设置为基本路线
RouterModule.forRoot([
{ path: '', component: AppComponent } // remove it
])
注意: AppComponent
在 angular
中默认调用,因此不需要调用它,如下所示:
@NgModule({
declarations: [
AppComponent,
],
bootstrap: [AppComponent] // bootstraping here by default
})
export class AppModule { }
这发生在我身上,因为我在 *ngFor
中有未命名的 <router-outlet>
。它为循环中的每次迭代加载。
在这种情况下,解决方案是为每个插座取一个唯一的名称,或者确保 DOM 中一次只有一个(也许带有 *ngIf
).
我自己也有类似的问题,我正在调用一个组件,然后通过路由器插座引用相同的组件。
<layout>
<router-outlet></router-outlet>
</layout>
而且出口路线也指向布局。
ngOnInit()
仅在所有指令实例化后挂钩一次。如果您在 ngOnInit()
内有订阅并且没有取消订阅,那么如果订阅的数据发生变化,它将再次 运行。
在下面的 Stackblitz 示例中,我在 ngOnInit()
内订阅并控制结果。它控制台两次,因为它加载一次并且数据更改并再次加载。
对我来说,这是因为我在 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 中的所有订阅。我只是 取消订阅 ngOnDestroy 中的所有订阅,这解决了我的问题。
这发生在我的子组件中。子组件将使用 *ngIf 显示何时满足特定条件。我的解决方案是 将 ngIf 替换为 ngClass。
在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">
这可能表明您错误地启动了该组件两次。这可能有以下几个原因:
您已将
中AppRoutingModule
导入到超过 1 个模块中。在您的应用中搜索它并确保您只将它包含在AppModule
.您已将
AppModule
导入另一个模块。根据第 1 点,这也会复制您的AppRoutingModule
。您的标记中有多个
<router-outlet>
。这个可能会让人困惑,因为如果你有多个嵌套路由,你实际上需要不止一个<router-outlet>
。但是,您需要检查每条具有子路由的路由是否只有 1<router-outlet>
。有时将每个嵌套组件复制到父组件中只是为了查看实际输出会更容易。然后你可以看看你是否错误地多了一个<router-outlet>
。您的包装器组件在 AppRoutingModule 和子组件内指定。通常我会有一个包装器组件来处理标准布局,如页眉、页脚、菜单等。然后,当我的
AppRoutingModule
中设置时,我的所有子路由都将位于其中。有时也可以在另一个组件中使用这个包装器组件,从而复制它,因为它已经在AppRoutingModule
中指定。这将导致额外的<router-outlet>
.
我将 XComponent 放在了
bootstrap: [AppComponent, XComponent]
在app.module.ts.
删除 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>
请注意,如果您这样做,它也可能最终调用构造函数两次。构造函数被调用两次的事实证明组件被创建了两次,这正是当组件位于条件从 true 到 false 到 true
的*ngIf
中时发生的情况
一个有用的调试方法是创建一个名为 rnd 的 class 变量,如下所示:
rnd = Math.random()
and console.log 它在构造函数和 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>
这发生在我身上,因为我在锚标记中有 arribute href="#"。不要在锚标签中使用。如果您按照 angular 代码使用 routerLink,这将得到修复,如下所示,并尽量避免在 appComponent.ts
中使用 ngInit(0 方法<a class="nav-link" [routerLink]="['/login']"> Sign In. </a>
我的解决方案是在组件中使用 ngIf
来防止未定义的输入,在你的情况下是:
<result-comp *ngIf="answers" [answers]="answers"></result-comp>