I've come across a situation a few times where I use a shared service in a component's ngOnInit
to update a value in another component, which just so happens to be a parent.
This results in the infamous Error: NG0100: Expression has changed after it was checked
in development mode, and change detection will not pick up the change. I understand why this happens and it is expected behaviour.
My strategy is to use a zero delay setTimeout()
to defer the code execution until after the initial change detection has finished. For reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#zero_delays
To me, it seems like a workaround for something very common, and I feel Angular probably has another solution I'm not aware of. So my question is: Is there an Angular way to update a parent component with a shared service in ngOnInit.
I have tried all other lifecycle hooks and they all result in the same error as well. Using a subject and an async pipe is the same.
Here's an example: a service that changes the font color of the main component when a child component is opened: https://stackblitz.com/edit/angular-ivy-kkvarp?file=src/app/global-style.service.ts
Service
export class GlobalStyleService {
private _green = false;
set green(value: boolean) {
// Uncomment to get an error
// this._green = value;
//For use in lifecycle hooks
//Delay setting until after change detection completes
setTimeout(() => (this._green = value));
}
get green() {
return this._green;
}
}
app.component.ts
export class AppComponent {
constructor(public globalStyle: GlobalStyleService) {}
}
app.component.html
<app-main [class.green]="globalStyle.green"></app-main>
styles.css
.green {
color: green;
}
main.component.html
<div class="container">
<h1>Main Component</h1>
<button (click)="testComponentOpen = !testComponentOpen">
Toggle Test Component
</button>
<app-test *ngIf="testComponentOpen"></app-test>
</div>
test.component.ts
export class TestComponent implements OnInit, OnDestroy {
constructor(private globalStyle: GlobalStyleService) {}
ngOnInit() {
this.globalStyle.green = true;
}
ngOnDestroy() {
this.globalStyle.green = false;
}
}
Opening the test component sets the service variable, which changes the style of the main component. If you set the variable without using setTimeout()
you will get the following error:
Error: NG0100: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value for 'green': 'false'. Current value: 'true'
And the text will not turn green until you trigger another round of change detection.
Using setTimeout()
works, but is there an Angular way? Something that explicitly defers the code execution if change detection is already in progress?