Phalgun
Phalgun

Reputation: 1494

Changing @Input() in child compoent properties prevents further updates from parent component

I have 2 components - Parent and Child. Please see stackblitz demo Parent's template looks like below - enter image description here

When 'Show Child' is clicked, isChild1Visible is set to true causing the Child component to display its template. enter image description here

When you click 'Hide me' the Child component sets isVisible to false, causing it to be hidden. Now when 'Show Child' is clicked again, the child component is not shown.

I add ngOnChanges() in the Child component to see the change detection, however I see the below When child component is initialized: enter image description here

After 'Show me' is clicked for 1st time enter image description here

After 'Hide me' is clicked, the ngOnChanges() does not print anything even when 'Show me' is clicked later on.

So, why does change detection stop working after input property is update in the child component?

Upvotes: 2

Views: 3095

Answers (3)

DEV
DEV

Reputation: 1736

Since data isChild1Visible is being passed as an primitive type(boolean) it will "Pass by Value."

Therefore, if passing an object, array, or the like, then it is Passed by Reference, and for primitive types like numbers, it is Passed by Value.

try this without adding any extra outputs :

MyAppChild1Component

export class MyAppChild1Component implements OnInit {
  @Input() isVisible;
  constructor() {}

  ngOnInit() {}
  onClick() {
    this.isVisible.value = false;
  }
}

<div *ngIf="isVisible.value">
  <span>
Child
</span>
  <button (click)="onClick()">Hide me</button>
</div>

AppComponent

export class AppComponent {
  isChild1Visible = { value: false };
  ngOnInit() {}
  onShowChild() {
    this.isChild1Visible.value = true;
  }
}

<div>
  <p>Parent</p>
  <button (click)="onShowChild()">show Child</button>
  <app-my-app-child1 [isVisible]="isChild1Visible"></app-my-app-child1>
</div>

Upvotes: 3

PsyGik
PsyGik

Reputation: 3675

That happens because isChild1Visible in app.component isn't updated when the child component sets isVisible.

First Click on onShowChild()

  • isChild1Visible = true
  • isVisible = true

Second Click on onClick() in child

  • isVisible = false;
  • isChild1Visible = true

Second Click on onShowChild()

  • isChild1Visible = true // => No changes here
  • isVisible = false

Since the value of isChild1Visible is not changed, Angular does not update anything.

In order to trigger Change Detection and keep values in sync, you need to use Two-Way binding.

<app-my-app-child1 [(isVisible)]="isChild1Visible"></app-my-app-child1>
export class MyAppChild1Component implements OnInit {
  @Input() isVisible;
  @Output() isVisibleChange = new EventEmitter<boolean>();

  constructor() {}

  ngOnInit() {}
  onClick() {
    this.isVisibleChange.emit(!this.isVisibleChange);
  }
}

Updated Sandbox

Upvotes: 1

Balastrong
Balastrong

Reputation: 4474

The issue

The first time you set isChild1Visible, with the one way binding it also sets the child isVisible to true.

From the inside, you set isVisible to false, so the div disappears as you have *ngIf="isVisible".

However, the outer isVisible is still true!

When you hit again the outer button, there's no changes to detect as the value remains the same, so nothing is passed to the child.

See this stackblitz demo with both booleans exposed.

Option 1: Explicitly listen to on output

If you want to control a component from the outside, you should keep the logic outside.

A possible way is to add an @Output on your child that is an EventEmitter that emits when you want to close the child.

The parent will listen to that event and do something, like setting isVisible to false.

You can see it on this updated stackblitz.

Option 2: Two-way binding

Similar to solution Option 1, you can create an emitter called with the same name of your input, ending with Change and trigger there your change.

In this case you only need to replace the binding with [(isVisible)]="isChild1Visible" and add isVisibleChange as @Output EventEmitter<any>.

Demo on stackblitz.

Upvotes: 8

Related Questions