I'm working on an Angular project (v16), and I have a parent component passing data to a child component via an @Input() property. However, when the parent updates the value, the child component doesn't seem to react to the change.
Here's the relevant code:
Parent component:
<app-child [item]="selectedItem"></app-child>
export class ParentComponent {
selectedItem = { id: 1, name: 'Item 1' };
updateItem() {
this.selectedItem.name = 'Updated Item';
}
}
Child component:
@Component({ selector: 'app-child', ... })
export class ChildComponent implements OnInit {
@Input() item: any;
ngOnInit() {
console.log('Initial item:', this.item);
}
}
When I click a button in the parent that runs updateItem(), I expect the child component to reflect the updated value, but it doesn't.
What I tried:
Using ngOnChanges() in the child, but it doesn't get triggered.
Logging the item in ngDoCheck(), but change detection doesn't pick up the change unless the reference changes.
Expected Behavior: The child should detect changes inside the item object, not just when the reference changes.
ngOnChanges()
is not triggered because the @Input()
reference stays the same.
You need to change the object reference, the easiest way is to copy the object, and then change the property you want to change:
updateItem() {
this.selectedItem = { ...this.selectedItem, name: 'Updated Item' };
}
This way, Angular sees an entirey new reference for selectedItem and it will trigger the update you want.
Basically if you update a property in an object "in place" (ie myobj.prop = "newprop"), there is no way for angular to detect that the object has changed unless they do an expensive deep equality check.
You can do it in several ways.
One way, as @Christo already has shown you, is to replace the entire object. So your component looks like this now:
export class ParentComponent {
selectedItem = { id: 1, name: 'Item 1' };
updateItem() {
this.selectedItem.name = 'Updated Item';
}
}
Another way is to explicitly tell Angular that you've made changes, with change detector.
export class ParentComponent {
private cdr = inject(ChangeDetectorRef)
selectedItem = { id: 1, name: 'Item 1' };
updateItem() {
this.selectedItem.name = 'Updated name';
this.cdr.markForCheck(); // <-- this bit tells Angular that you have changes in your component
}
}
Another couple of ways would be to use a signal, as someone mentioned, or an observable:
interface Item = { id: number, name: string }
export class ParentComponent {
selectedItem = signal<Item>({ id: 1, name: 'Item 1' })
selectedItem$ = new BehaviorSubject({ id: 2, name: 'Item 2' })
updateItem() {
const id = this.selectedItem().id;
this.selectedItem.set({ id, name: 'New name' })
}
updateItemViaObservable() {
const id = this.selectedItem$.value.id
this.selectedItem$.next({ id, name: 'An observable name' })
}
}
There are other ways as well. I would recommend using a signal, if you have a newer Angular project, or the first version I posted (and Christo has), if not.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With