Angular 4 - “表达式在检查后发生了变化"使用 NG-IF 时出错 [英] Angular 4 - "Expression has changed after it was checked" error while using NG-IF

查看:13
本文介绍了Angular 4 - “表达式在检查后发生了变化"使用 NG-IF 时出错的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我设置了一个 service 来跟踪登录的用户.该服务返回一个 Observable 并且所有订阅的组件都会被通知(目前只有一个组件订阅它).

服务:

private subject = new Subject();发送消息(消息:布尔值){this.subject.next(消息);}getMessage(): Observable{返回 this.subject.asObservable();}

Root App Component:(这个组件订阅了observable)

ngAfterViewInit(){this.subscription = this._authService.getMessage().subscribe(message => { this.user = message; });}

欢迎组件:

ngOnInit() {const checkStatus = this._authService.checkUserStatus();this._authService.sendMessage(checkStatus);}

App Component Html:(这是发生错误的地方)

<小时>

我想做什么:

我希望每个组件(除了根应用程序组件)将用户登录状态发送到根应用程序组件,以便我可以在根应用程序组件 Html 中操作 UI.

问题:

在初始化欢迎组件时出现以下错误.

表达式在检查后发生了变化.以前的值:'未定义'.当前值:真".

请注意此错误发生在此 *ngIf="user" 表达式上,该表达式位于根应用组件 HTML 文件中.

谁能解释这个错误的原因以及我该如何解决这个问题?

附带说明:如果您认为有更好的方法来实现我的目标,请告诉我.

更新 1:

将以下内容放入 constructor 可以解决问题,但不想为此使用 constructor 所以这似乎不是一个好的解决方案.

欢迎组件:

构造函数(私有_authService:AuthenticationService){const checkStatus = this._authService.checkUserStatus();this._authService.sendMessage(checkStatus);}

根应用组件:

构造函数(私有_authService:AuthenticationService){this.subscription = this._authService.getMessage().subscribe(message => { this.usr = message; });}

更新 2:

这是 plunkr.要查看错误,请检查浏览器控制台.当应用程序加载布尔值 true 时应该显示,但我在控制台中收到错误.

请注意,这个 plunkr 是我的主应用程序的一个非常基本的版本.由于应用程序有点大,我无法上传所有代码.但是 plunkr 完美地演示了错误.

解决方案

这意味着变更检测周期本身似乎导致了变更,这可能是偶然的(即变更检测周期以某种方式导致了它)或故意的.如果您确实在更改检测周期中有意更改了某些内容,那么这应该会重新触发新一轮的更改检测,而这里不会发生这种情况.此错误将在 prod 模式下被抑制,但这意味着您的代码存在问题并导致神秘问题.

在这种情况下,具体问题是您正在更改影响父级的子级更改检测周期中的某些内容,并且这不会重新触发父级的更改检测,即使像 observable 这样的异步触发器通常会这样做.它不重新触发父级循环的原因是因为这违反了单向数据流,并且可能造成子级重新触发父级更改检测循环的情况,然后重新触发子级,然后再次触发父级等等,并导致应用程序中的无限变化检测循环.

听起来好像我在说孩子不能向父组件发送消息,但事实并非如此,问题是在更改检测期间孩子无法向父组件发送消息循环(例如生命周期钩子),它需要在外部发生,如响应用户事件.

此处的最佳解决方案是通过创建一个不是导致更新的组件的父组件的新组件来停止违反单向数据流,从而无法创建无限的更改检测循环.这在下面的 plunkr 中进行了演示.

添加了子项的新 app.component:

<app-message></app-message><路由器插座></路由器插座>

消息组件:

@Component({模块 ID:module.id,选择器:'应用消息',templateUrl: 'message.component.html'})导出类 MessageComponent 实现 OnInit {message$: Observable;构造函数(私有消息服务:消息服务){}ngOnInit(){this.message$ = this.messageService.message$;}}

模板:

<div *ngIf="message$ | async as message" class="alert alert-success">{{message}}</div>

稍微修改的消息服务(只是一个稍微简洁的结构):

@Injectable()导出类 MessageService {私人主题 = 新主题<任何>();message$: Observable= this.subject.asObservable();发送消息(消息:字符串){console.log('发送消息');this.subject.next(message);}清除消息(){this.subject.next();}}

这比仅仅让变更检测正常工作而没有创建无限循环的风险有更多好处.它还可以让您的代码更加模块化并更好地隔离责任.

https://plnkr.co/edit/4Th7m0Liovfgd1Z3ECWh?p=preview

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:

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)

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)

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

The issue:

I get the following error when the Welcome Component is 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.

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:

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.

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:

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.

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.

解决方案

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.

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.

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.

New app.component with child added:

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

https://plnkr.co/edit/4Th7m0Liovfgd1Z3ECWh?p=preview

这篇关于Angular 4 - “表达式在检查后发生了变化"使用 NG-IF 时出错的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆