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
Declare this line in constructor
private cd: ChangeDetectorRef
after that in ngAfterviewInit call like this
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
Your Plunkr Answer Here Please check with 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();
}
}
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:
ngOnInit
, ngOnChanges
and ngDoCheck
ngAfterViewInit
But what if a lifecycle hook contains a code that changes the property again, and a Change Detection Round isn't run? Or what if a lifecycle hook contains a code that causes another Change Detection Round and 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 Round after the first, to be sure that nothing is changed. Pay attention: this happens only in development mode.
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 Round and Angular is intelligent enough to understand what's happening.
But not all the events cause a Change Detection Round, and yours is an example: an Observable does not trigger the change detection strategy.
What you have to do is to awake Angular triggering a round of change detection. You can use an EventEmitter
, a timeout, whatever causes an event.
My favorite solution is using window.setTimeout
:
this.subscription = this._authService.getMessage().subscribe(message => window.setTimeout(() => this.usr = message, 0));
This solves the problem.
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
.
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.
To understand the error, read:
You case falls under the Synchronous event broadcasting
category:
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.
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.
As explained in the article you have two general approaches to working around this error:
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 Angular for more details.
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:
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.
But I'm also wondering how come this._authService.checkUserStatus()
is synchronous?
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
Don't change the var in ngOnInit, change it in constructor
constructor(private apiService: ApiService) {
this.apiService.navbarVisible(false);
}