Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 4 - "Expression has changed after it was checked" error while using NG-IF

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.

like image 419
Skywalker Avatar asked Nov 01 '17 18:11

Skywalker


People also ask

How do you fix expression has changed after it was checked?

Navigate up the call stack until you find a template expression where the value displayed in the error has changed. Ensure that there are no changes to the bindings in the template after change detection is run. This often means refactoring to use the correct component lifecycle hook for your use case.

How do I use ngAfterViewInit?

ngAfterViewInit() is called after all child components are initialized and checked. In the example above, ngAfterViewInit() gets called one time after ngDoCheck. Triggering the clickMe() function WILL NOT trigger ngAfterViewInit().

Can't bind to ngIf since it isn't a known property of?

Solution for Angular Can't bind to 'ngIf' since it isn't a known property of 'div' There are multiple ways to fix this. Incorrect ngIf syntax One way, This is due to an error caused due to misspelling capital 'I' for div. To solve this, Import CommonModule or BroswerModule or both into app.


1 Answers

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

like image 95
bryan60 Avatar answered Sep 23 '22 00:09

bryan60