Reputation: 39210
In the child, I have a field data
and on a click I alter contents of it. Then, I want an effect in the parent to fire detecting said change. For some reason, the effect doesn't fire when I alter the model input in the child (however, the effect fires when setting the signal in the parent.
The child has this.
export class ChildComponent {
data = model({} as Vm);
onClick() {
console.log("Click alters the data.");
// this.data.set(this.data());
this.data.update(a => a);
}
}
The parent has this.
export class ParentComponent {
constructor() {
interval(1000).subscribe(a => console.log("poll: ", this.data()));
effect(() => { console.log("effect: ", this.data()) });
}
data = signal({} as Vm);
ngOnInit() { this.data.set(this.getData()); }
}
I see the intial effect displaying an empty object. Then, I see the update from parent's init method. I also see the secondwise check displaying the value altered in the child. Yet - the effect won't fire again, only the aforementioned twice.
I'm out of ideas on how to troubleshoot it further. Checking the official docs, I see that it's supposed to work (which it, sort of, does!) but I'm entirely certain if I perform the alteration in the child component fully correctly.
(In the actual scenario, I'll be only altering a field in an object in an array that is a part of the data
model input but that's the next step. First, I want to understand why doing set(...)
nor update(...)
triggers the effect in the partent.
Upvotes: 0
Views: 371
Reputation: 57696
The signal fires only when the actual value changes, since arrays and objects are stored as references in memory, if you change the internal properties the memory reference does not change, hence it won't be detected as a change. It also does not change when you just return the object as is.
Signals And Array Mutability In Angular 18 - Ben Nadel
GitHub issue - Angular 17 - Mutable signals are not supported
You can use object destructuring (for objects) or .slice()
array method (only arrays) to create a new memory reference and this will be propagated to the parent. Since the memory reference is changed, it will be picked up by the signal as a new change.
This explanation applies only for effect
and computed
since they react only to signal changes.
Sometimes you might notice the HTML updating fine for inner properties of a signal containing an object ( e.g: {{ data()?.qwerty?.test }}
). This is not due to signals, but due to change detection running ( Click event, running cdr.detectChanges() ), Angular will fire a change detection cycle and the latest values are updated in html for the properties nested inside the signal. This does not mean that signals support mutable objects. It's simply a side effect of change detection running.
@Component({
selector: 'app-child',
standalone: true,
template: `
<button (click)="onClick()">click</button>
`,
})
export class ChildComponent {
data = model({});
onClick() {
console.log('Click alters the data.');
// this.data.set(this.data());
// object destructuring creates new memory reference.
this.data.update((a: any) => ({ ...a }));
}
}
import { Component, effect, model, signal } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { interval } from 'rxjs';
@Component({
selector: 'app-child',
standalone: true,
template: `
<button (click)="onClick()">click</button>
`,
})
export class ChildComponent {
data = model({});
onClick() {
console.log('Click alters the data.');
// this.data.set(this.data());
this.data.update((a: any) => ({ ...a }));
}
}
@Component({
selector: 'app-root',
standalone: true,
imports: [ChildComponent],
template: `
<app-child [(data)]="data" (dataChange)="dataChange($event)"/>
`,
})
export class App {
data = signal({});
constructor() {
// interval(1000).subscribe((a: any) => console.log('poll: ', this.data()));
effect(() => {
console.log('effect: ', this.data());
});
}
ngOnInit() {
// this.data.set(this.getData());
}
dataChange(e: any) {
console.log(e);
}
}
bootstrapApplication(App);
Upvotes: 2