Angular 4 - 使用NG-IF时"检查后表情发生了变化"错误

Sky*_*ker 13 javascript rxjs typescript angular

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

服务:

private subject = new Subject<any>();

sendMessage(message: boolean) {
   this.subject.next( message );
}

getMessage(): Observable<any> {
   return this.subject.asObservable();
} 
Run Code Online (Sandbox Code Playgroud)

Root App组件:(此组件订阅了observable)

ngAfterViewInit(){
   this.subscription = this._authService.getMessage().subscribe(message => { this.user = message; });
}
Run Code Online (Sandbox Code Playgroud)

欢迎组件:

ngOnInit() {
  const checkStatus = this._authService.checkUserStatus();
  this._authService.sendMessage(checkStatus);
}
Run Code Online (Sandbox Code Playgroud)

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

<div *ngIf="user"><div>
Run Code Online (Sandbox Code Playgroud)

我正在做的事情:

我希望每个组件(Root App Component除外)将用户登录状态发送到Root App Component,这样我就可以在Root App Component Html中操作UI.

问题:

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

Expression has changed after it was checked. Previous value: 'undefined'. Current value: 'true'.
Run Code Online (Sandbox Code Playgroud)

请注意,此*ngIf="user"表达式发生此错误,该表达式位于Root App Components HTML文件中.

有人可以解释这个错误的原因以及我如何解决这个问题?

旁注:如果您认为有更好的方法来实现我想要做的事情,请告诉我.

更新1:

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

欢迎组件:

constructor(private _authService: AuthenticationService) {
  const checkStatus = this._authService.checkUserStatus();
  this._authService.sendMessage(checkStatus);
 }
Run Code Online (Sandbox Code Playgroud)

Root App组件:

constructor(private _authService: AuthenticationService){
   this.subscription = this._authService.getMessage().subscribe(message => { this.usr = message; });
}
Run Code Online (Sandbox Code Playgroud)

更新2:

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

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

bry*_*n60 17

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

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

听起来好像我说儿童无法向父组件发送消息,但情况并非如此,问题是儿童在更改检测周期内无法向父母发送消息(例如作为生命周期挂钩),它需要在外部发生,就像响应用户事件一样.

这里最好的解决方案是通过创建一个新组件来阻止违反单向数据流,该新组件不是导致更新的组件的父组件,因此无法创建无限更改检测循环.这在下面的plunkr中得到了证明.

添加了子项的新app.component:

<div class="col-sm-8 col-sm-offset-2">
      <app-message></app-message>
      <router-outlet></router-outlet>
</div>
Run Code Online (Sandbox Code Playgroud)

消息组件:

@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$;
   }
}
Run Code Online (Sandbox Code Playgroud)

模板:

<div *ngIf="message$ | async as message" class="alert alert-success">{{message}}</div>
Run Code Online (Sandbox Code Playgroud)

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

@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();
    }
}
Run Code Online (Sandbox Code Playgroud)

这不仅仅是让更改检测正常工作,而且没有创建无限循环的风险.它还使您的代码更加模块化,并更好地隔离责任.

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