I am trying to work with Angular 17 effects using the toSignal() interop class to convert an Observable to a signal. I am registering an effect for that signal in the constructor, and I am expecting to see the effect trigger every time next is called on the BehaviorSubject
What I am seeing is that the signal called in the for loop of the template is updating as I would expect, and a new item gets rendered to the list. However, the effect method to log out the changes is only being called once.
@Component({
selector: 'app-dashboard',
standalone: true,
imports: [],
template: `
<button (click)="updateState()">
Update State
</button>
<ol>
@for (s of signalState(); track s) {
<li>{{s}}</li>
}
</ol>
`,
styles: ``
})
export class DashboardComponent {
testBehaviorSubject = new BehaviorSubject<string[]>([]);
testObservable = this.testBehaviorSubject.asObservable();
signalState = toSignal(this.testObservable);
history: string[] = [];
constructor() {
effect(() => console.log(`State updated: `, this.signalState()));
}
updateState() {
this.history.push('Clicked');
this.testBehaviorSubject.next(this.history);
}
}
The DOM is updated when the button is clicked
Why are the effects not firing in the same way?
clickschedules a Change Detection cycle;@forreads the expressions of signalState()and creates output for the current value of the signal (so the list will always be re-rendered correctly).@fordoes NOT listen to signal changes, it just returns a new output for the new input;updateState(), you are pushing the same array as the next value. Signals will only notify its consumers when the new value is actually new. To check it, a signal compares values usingObject.is()(if no custom equality check function is set). Because it is the same array,Object.is()will return true and the signal will not notify consumers (effect(), in your code) that the value is changed1.You have 2 options:
1 Even more - signal will not set the new value, if the equality check function shows that the new value is equal to the old one. But in your case, you are mutating the array by reference (
history.push()). The signal can not see this mutation but the array itself will be mutated anyway - that's why@forreceives all the items, even if the signal skipped the change.