Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I make the child component detect changes to properties inside an object passed via @Input()?

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.

like image 812
Franklin Samuvel Avatar asked Aug 31 '25 03:08

Franklin Samuvel


2 Answers

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.

like image 136
Christo Avatar answered Sep 02 '25 19:09

Christo


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.

like image 24
Zlatko Avatar answered Sep 02 '25 19:09

Zlatko