typescript Angular 4 - 使用 NG-IF 时出现“检查后表达式已更改”错误
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/47061525/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Angular 4 - "Expression has changed after it was checked" error while using NG-IF
提问by Skywalker
I setup a service
to keep track of logged in users. That service returns an Observable and all components that subscribe
to it are notified (so far only a single component subscribe to it).
我设置了一个service
来跟踪登录的用户。该服务返回一个 Observable 并subscribe
通知它的所有组件(到目前为止只有一个组件订阅它)。
Service:
服务:
private subject = new Subject<any>();
sendMessage(message: boolean) {
this.subject.next( message );
}
getMessage(): Observable<any> {
return this.subject.asObservable();
}
Root App Component:(this component subscribes to the observable)
Root App 组件:(这个组件订阅了observable)
ngAfterViewInit(){
this.subscription = this._authService.getMessage().subscribe(message => { this.user = message; });
}
Welcome Component:
欢迎组件:
ngOnInit() {
const checkStatus = this._authService.checkUserStatus();
this._authService.sendMessage(checkStatus);
}
App Component Html:(this is where the error occurs)
App Component Html:(这是发生错误的地方)
<div *ngIf="user"><div>
What I'm trying to do:
我正在尝试做的事情:
I want every component (except the Root App Component) to send the users logged-in state to the Root App Component so I can manipulate the UI within the Root App Component Html.
我希望每个组件(除了 Root App Component)将用户登录状态发送到 Root App 组件,以便我可以在 Root App Component Html 中操作 UI。
The issue:
问题:
I get the following error when the Welcome Componentis initialised.
初始化欢迎组件时出现以下错误。
Expression has changed after it was checked. Previous value: 'undefined'. Current value: 'true'.
Please note this error occurs on this *ngIf="user"
expression which is located within Root App Components HTML file.
请注意此错误发生在*ngIf="user"
位于根应用程序组件 HTML 文件中的此表达式上。
Can someone explain the reason for this error and how I can fix this?
有人可以解释这个错误的原因以及我如何解决这个问题吗?
On a side note: If you think theres a better way to achieve what I'm trying to do then please let me know.
附带说明:如果您认为有更好的方法来实现我正在尝试做的事情,请告诉我。
Update 1:
更新 1:
Putting the following in the constructor
solves the issue but don't want to use the constructor
for this purpose so it seems it's not a good solution.
将以下内容放入constructor
解决了问题,但不想将其constructor
用于此目的,因此这似乎不是一个好的解决方案。
Welcome Component:
欢迎组件:
constructor(private _authService: AuthenticationService) {
const checkStatus = this._authService.checkUserStatus();
this._authService.sendMessage(checkStatus);
}
Root App Component:
根应用组件:
constructor(private _authService: AuthenticationService){
this.subscription = this._authService.getMessage().subscribe(message => { this.usr = message; });
}
Update 2:
更新 2:
Here's the plunkr. To see the error check the browser console. When the app loads a boolean value of true should be displayed but I get the error in the console.
这是plunkr。要查看错误,请检查浏览器控制台。当应用程序加载布尔值 true 时,应该显示,但我在控制台中收到错误消息。
Please note that this plunkr is a very basic version of my main app. As the app is bit large I couldn't upload all the code. But the plunkr demonstrates the error perfectly.
请注意,这个 plunkr 是我的主应用程序的一个非常基本的版本。由于应用程序有点大,我无法上传所有代码。但是 plunkr 完美地演示了错误。
回答by bryan60
What this means is that the change detection cycle itself seems to have caused a change, which may have been accidental (ie the change detection cycle caused it somehow) or intentional. If you do change something in a change detection cycle on purpose, then this should retrigger a new round of change detection, which is not happening here. This error will be suppressed in prod mode, but means you have issues in your code and cause mysterious issues.
这意味着变更检测周期本身似乎引起了变更,这可能是偶然的(即变更检测周期以某种方式导致它)或有意。如果您确实在更改检测周期中有意更改了某些内容,那么这应该会重新触发新一轮的更改检测,而这里不会发生这种情况。此错误将在 prod 模式下被抑制,但这意味着您的代码存在问题并导致神秘问题。
In this case, the specific issue is that you're changing something in a child's change detection cycle which affects the parent, and this will not retrigger the parent's change detection even though asynchronous triggers like observables usually do. The reason it doesn't retrigger the parent's cycle is becasue this violates unidirectional data flow, and could create a situation where a child retriggers a parent change detection cycle, which then retriggers the child, and then the parent again and so on, and causes an infinite change detection loop in your app.
在这种情况下,具体问题是您正在更改影响父级的子级更改检测周期中的某些内容,并且即使像 observables 这样的异步触发器通常会这样做,这也不会重新触发父级的更改检测。它不重新触发父级循环的原因是因为这违反了单向数据流,并且可能造成子级重新触发父级更改检测循环的情况,然后重新触发子级,然后再次触发父级等等,并导致应用程序中的无限变化检测循环。
It might sound like I'm saying that a child can't send messages to a parent component, but this is not the case, the issue is that a child can't send a message to a parent during a change detection cycle (such as life cycle hooks), it needs to happen outside, as in in response to a user event.
听起来好像我在说孩子不能向父组件发送消息,但事实并非如此,问题是在变更检测周期(例如作为生命周期钩子),它需要在外部发生,如响应用户事件。
The best solution here is to stop violating unidirectional data flow by creating a new component that is not a parent of the component causing the update so that an infinite change detection loop cannot be created. This is demonstrated in the plunkr below.
此处的最佳解决方案是通过创建一个不是导致更新的组件的父级的新组件来停止违反单向数据流,从而无法创建无限的更改检测循环。这在下面的 plunkr 中进行了演示。
New app.component with child added:
添加了子项的新 app.component:
<div class="col-sm-8 col-sm-offset-2">
<app-message></app-message>
<router-outlet></router-outlet>
</div>
message component:
消息组件:
@Component({
moduleId: module.id,
selector: 'app-message',
templateUrl: 'message.component.html'
})
export class MessageComponent implements OnInit {
message$: Observable<any>;
constructor(private messageService: MessageService) {
}
ngOnInit(){
this.message$ = this.messageService.message$;
}
}
template:
模板:
<div *ngIf="message$ | async as message" class="alert alert-success">{{message}}</div>
slightly modified message service (just a slightly cleaner structure):
稍微修改的消息服务(只是一个稍微干净的结构):
@Injectable()
export class MessageService {
private subject = new Subject<any>();
message$: Observable<any> = this.subject.asObservable();
sendMessage(message: string) {
console.log('send message');
this.subject.next(message);
}
clearMessage() {
this.subject.next();
}
}
This has more benefits than just letting change detection work properly with no risk of creating infinite loops. It also makes your code more modular and isolates responsibility better.
这比仅仅让变更检测正常工作而没有创建无限循环的风险有更多的好处。它还使您的代码更加模块化并更好地隔离责任。
回答by Cristian Traìna
Nice question, so, what causes the problem? What's the reason for this error? We need to understand how Angular change detection works, I'm gonna explain briefly:
好问题,那么,是什么导致了这个问题?这个错误的原因是什么?我们需要了解 Angular 变化检测的工作原理,我将简要解释一下:
- You bind a property to a component
- You run an application
- An event occurs (timeouts, ajax calls, DOM events, ...)
- The bound property is changed as an effect of the event
- Angular also listens to the event and runs a Change Detection Round
- Angular updates the view
- Angular calls the lifecycle hooks
ngOnInit
,ngOnChanges
andngDoCheck
- Angular run a Change Detection Roundin all the children components
- Angular calls the lifecycle hooks
ngAfterViewInit
- 您将属性绑定到组件
- 你运行一个应用程序
- 事件发生(超时、ajax 调用、DOM 事件、...)
- 绑定属性随着事件的影响而改变
- Angular 还会监听事件并运行变更检测轮
- Angular 更新视图
- Angular 调用生命周期钩子
ngOnInit
,ngOnChanges
并且ngDoCheck
- Angular在所有子组件中运行变更检测回合
- Angular 调用生命周期钩子
ngAfterViewInit
But what if a lifecycle hook contains a code that changes the property again, and a Change Detection Roundisn't run? Or what if a lifecycle hook contains a code that causes another Change Detection Roundand the code enters into a loop? This is a dangerous eventuality and Angular prevents it paying attention to the property to don't change in the while or immediately after. This is achieved performing a second Change Detection Roundafter the first, to be sure that nothing is changed. Pay attention: this happens only in development mode.
但是,如果生命周期挂钩包含再次更改属性的代码,并且更改检测轮未运行,该怎么办?或者,如果生命周期钩子包含导致另一轮变更检测的代码并且代码进入循环怎么办?这是一个危险的可能性,Angular 阻止它注意属性不要在一段时间内或之后立即更改。这是在第一轮之后执行第二轮变更检测来实现的,以确保没有任何变化。注意:这只发生在开发模式。
If you trigger two events at the same time (or in a very small time frame), Angular will fire two Change Detection Cycles at the same time and there are no problems in this case, because Angular since both the events trigger a Change Detection Roundand Angular is intelligent enough to understand what's happening.
如果您同时(或在非常短的时间范围内)触发两个事件,Angular 将同时触发两个变更检测周期,在这种情况下没有问题,因为 Angular 因为这两个事件都触发了变更检测周期Angular 足够聪明,可以理解正在发生的事情。
But not all the events cause a Change Detection Round, and yours is an example: an Observable does not trigger the change detection strategy.
但并非所有事件都会导致Change Detection Round,而您的事件就是一个例子:Observable 不会触发变更检测策略。
What you have to do is to awake Angular triggering a round of change detection. You can use an EventEmitter
, a timeout, whatevercauses an event.
你要做的就是唤醒Angular,触发一轮变化检测。您可以使用EventEmitter
, 超时,无论导致事件的原因。
My favorite solution is using window.setTimeout
:
我最喜欢的解决方案是使用window.setTimeout
:
this.subscription = this._authService.getMessage().subscribe(message => window.setTimeout(() => this.usr = message, 0));
This solves the problem.
这解决了问题。
回答by Piyush Patel
Declare this line in constructor
在构造函数中声明这一行
private cd: ChangeDetectorRef
after that in ngAfterviewInit call like this
之后在 ngAfterviewInit 中这样调用
ngAfterViewInit() {
// it must be last line
this.cd.detectChanges();
}
it will resolve your issue because DOM element boolean value doesnt get change. so its throw exception
它将解决您的问题,因为 DOM 元素布尔值不会改变。所以它的抛出异常
Your Plunkr Answer Here Please check with AppComponent
您的 Plunkr 答案在这里请与 AppComponent 核对
import { AfterViewInit, ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { Subscription } from 'rxjs/Subscription';
import { MessageService } from './_services/index';
@Component({
moduleId: module.id,
selector: 'app',
templateUrl: 'app.component.html'
})
export class AppComponent implements OnDestroy, OnInit {
message: any = false;
subscription: Subscription;
constructor(private messageService: MessageService,private cd: ChangeDetectorRef) {
// subscribe to home component messages
//this.subscription = this.messageService.getMessage().subscribe(message => { this.message = message; });
}
ngOnInit(){
this.subscription = this.messageService.getMessage().subscribe(message =>{
this.message = message
console.log(this.message);
this.cd.detectChanges();
});
}
ngOnDestroy() {
// unsubscribe to ensure no memory leaks
this.subscription.unsubscribe();
}
}
回答by Max Koretskyi
To understand the error, read:
要了解错误,请阅读:
You case falls under the Synchronous event broadcasting
category:
您的案例属于以下Synchronous event broadcasting
类别:
This pattern is illustrated by this plunker. The application is designed to have a child component emitting an event and a parent component listening to this event. The event causes some of the parent properties to be updated. And these properties are used as input binding for the child component. This is also an indirect parent property update.
这个 plunker说明了这种模式。该应用程序设计为具有一个发出事件的子组件和一个侦听此事件的父组件。该事件会导致更新某些父属性。并且这些属性用作子组件的输入绑定。这也是一个间接的父属性更新。
In your case the parent component property that is updated is user
and this property is used as input binding to *ngIf="user"
. The problem is that you're triggering an event this._authService.sendMessage(checkStatus)
as part of change detection cycle because you're doing it from lifecycle hook.
在您的情况下,更新的父组件属性是,user
并且此属性用作输入绑定到*ngIf="user"
. 问题是您正在触发一个事件this._authService.sendMessage(checkStatus)
作为变更检测周期的一部分,因为您是从生命周期钩子中进行的。
As explained in the article you have two general approaches to working around this error:
正如文章中所述,您有两种通用方法可以解决此错误:
- Asynchronous update- this allows triggering an event outside of change detection process
- Forcing change detection- this adds additional change detection run between the current run and the verification stage
- 异步更新- 这允许在变更检测过程之外触发事件
- 强制变更检测- 这会在当前运行和验证阶段之间添加额外的变更检测运行
First you have to answer the question if there's any need to trigger the even from the lifecycle hook. If you have all the information you need for the even in the component constructor I don't think that's the bad option. See The essential difference between Constructor and ngOnInit in Angularfor more details.
首先,您必须回答是否需要从生命周期钩子触发偶数的问题。如果您在组件构造函数中拥有所需的所有信息,我认为这不是一个糟糕的选择。有关更多详细信息,请参阅Angular 中构造函数和 ngOnInit 之间的本质区别。
In your case I would probably go with either asynchronous event triggering instead of manual change detection to avoid redundant change detection cycles:
在您的情况下,我可能会使用异步事件触发而不是手动更改检测来避免冗余更改检测周期:
ngOnInit() {
const checkStatus = this._authService.checkUserStatus();
Promise.resolve(null).then(() => this._authService.sendMessage(checkStatus););
}
or with asynchronous event processing inside the AppComponent:
或者在 AppComponent 内部使用异步事件处理:
ngAfterViewInit(){
this.subscription = this._authService.getMessage().subscribe(Promise.resolve(null).then((value) => this.user = message));
The approach I've shown above is used by ngModel in the implementation.
我上面展示的方法由ngModel 在 implementation 中使用。
But I'm also wondering how come this._authService.checkUserStatus()
is synchronous?
但我也想知道为什么this._authService.checkUserStatus()
是同步的?
回答by Ante Jablan Adamovi?
I recently encountered the same issue after migration to Angular 4.x, a quick solution is to wrap each part of the code which causes the ChangeDetection
in setTimeout(() => {}, 0) // notice the 0 it's intentional
.
我最近在迁移到 Angular 4.x 后遇到了同样的问题,一个快速的解决方案是将导致ChangeDetection
in setTimeout(() => {}, 0) // notice the 0 it's intentional
.
This way it will push the emit
AFTER the life-cycle hook
therefore not cause change detection error.
While I am aware this is a pretty dirty solution it's a viable quickfix.
这样它会推动emit
AFTER,life-cycle hook
因此不会导致更改检测错误。虽然我知道这是一个非常肮脏的解决方案,但它是一个可行的 quickfix。
回答by Googlian
Don't change the var in ngOnInit, change it in constructor
不要更改 ngOnInit 中的 var,在构造函数中更改它
constructor(private apiService: ApiService) {
this.apiService.navbarVisible(false);
}