Which @angular/* package(s) are the source of the bug?
core
Is this a regression?
No
Description
Go to this stackblitz
Click on the Toggle state button.
item is visible appears
Click again on the Toggle state button.
item is visible disappears
Click again on the Toggle state button.
Nothing happens! I would expect the item is visible element to appear again.
Here is the code of the stackblitz:
import 'zone.js/dist/zone';
import {
ChangeDetectionStrategy,
Component,
Directive,
effect,
signal,
Input,
OnChanges,
OnDestroy,
SimpleChanges,
computed,
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { bootstrapApplication } from '@angular/platform-browser';
interface State {
isInDom: boolean;
shouldBeInDom: boolean;
}
const createModel = () => {
const state = signal<State>({
isInDom: false,
shouldBeInDom: false,
});
const updateState = (changes: Partial<State>) =>
state.update((value) => ({
...value,
...changes,
}));
const toggle = () => updateState({ shouldBeInDom: !state().shouldBeInDom });
const setDomState = (isInDom: boolean) => updateState({ isInDom });
return { state, toggle, setDomState };
};
@Directive({
standalone: true,
selector: '[myDirective]',
})
export class MyDirective implements OnChanges, OnDestroy {
@Input({ required: true })
myDirective!: ReturnType<typeof createModel>;
async ngOnChanges(changes: SimpleChanges) {
// Comment the following line to have the issue on display:
await 0;
this.myDirective.setDomState(true);
}
async ngOnDestroy() {
// Uncomment the following line as a work-around to fix the issue:
// await 0;
this.myDirective.setDomState(false);
}
}
@Component({
selector: 'my-component',
standalone: true,
// Comment the following line as a work-around to fix the issue:
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [CommonModule, MyDirective],
template: `
<div *ngIf="state().shouldBeInDom" [myDirective]="model">item is visible</div>
`,
})
export class MyComponent {
model = createModel();
// Uncomment the following line as a work-around to fix the issue:
// state = this.model.state;
state = computed(() => this.model.state());
constructor() {
// Uncomment the following line as a work-around to fix the issue:
// effect(() => console.log(this.state()));
}
}
@Component({
selector: 'my-app',
standalone: true,
imports: [CommonModule, MyComponent],
template: `
<button (click)="c.model.toggle()">Toggle state</button>
<my-component #c></my-component>
`,
})
export class App {}
bootstrapApplication(App);
Please provide a link to a minimal reproduction of the bug
https://stackblitz.com/edit/angular-nltu6v?file=src/main.ts
Please provide the exception or error you saw
There is no exception, no explanation.
Please provide the environment you discovered this bug in (run ng version)
Angular CLI: 16.0.1
Node: 18.16.0
Package Manager: npm 9.5.1
OS: linux x64
Angular: 16.0.1
... animations, cli, common, compiler, compiler-cli, core, forms
... platform-browser, platform-browser-dynamic, router
Package Version
---------------------------------------------------------
@angular-devkit/architect 0.1600.1
@angular-devkit/build-angular 16.0.1
@angular-devkit/core 16.0.1
@angular-devkit/schematics 16.0.1
@schematics/angular 16.0.1
ng-packagr 16.0.1
rxjs 7.8.1
typescript 5.0.4
Anything else?
What triggers the bug seems to be the fact that the signal is synchronously changed either from ngOnChanges or from ngOnDestroy in the directive. If this is done asynchronously, the problem does not happen.
This makes me think about the famous ExpressionChangedAfterItHasBeenCheckedError issue (even if there is no exception displayed in this case), the idea is the same: there is an issue when updating data at some specific times.
As mentioned in the comments of the code, if any of the following modifications is done (alone), the bug does not happen:
- if
state is a direct reference to this.model.state in MyComponent instead of being a computed
- if an effect that reads
this.state() is added in the constructor of MyComponent
- if
MyComponent is not OnPush
Which @angular/* package(s) are the source of the bug?
core
Is this a regression?
No
Description
Go to this stackblitz
Click on the
Toggle statebutton.item is visibleappearsClick again on the
Toggle statebutton.item is visibledisappearsClick again on the
Toggle statebutton.Nothing happens! I would expect the
item is visibleelement to appear again.Here is the code of the stackblitz:
Please provide a link to a minimal reproduction of the bug
https://stackblitz.com/edit/angular-nltu6v?file=src/main.ts
Please provide the exception or error you saw
Please provide the environment you discovered this bug in (run
ng version)Anything else?
What triggers the bug seems to be the fact that the signal is synchronously changed either from
ngOnChangesor fromngOnDestroyin the directive. If this is done asynchronously, the problem does not happen.This makes me think about the famous
ExpressionChangedAfterItHasBeenCheckedErrorissue (even if there is no exception displayed in this case), the idea is the same: there is an issue when updating data at some specific times.As mentioned in the comments of the code, if any of the following modifications is done (alone), the bug does not happen:
stateis a direct reference tothis.model.stateinMyComponentinstead of being acomputedthis.state()is added in the constructor ofMyComponentMyComponentis notOnPush