1

I have two components; parent and child, which communicates via @Input and @Output property decorators. i.e. Parent passes an input object named config to the child and listens to the child's emitted events.

where config object type is,

config:any = {
    a: number;
    b: number;
}

here to keep things simple config.a is managed by child so parent never changes it and similarly config.b is managed by the parent.

I observed following strange behaviors while setting up this communication model.

  1. Parent passes the required input via property binding i.e.

[config]="config"

normally property binding works only one way, but here its behaving as two way binding. i.e. when ever parent updates config.b in its own scope, child's config also gets updated and vice versa.

  1. child wants to be notified of any change which parent makes to its own config object, so it implements the OnChanges life cycle hook, to perform some inner functionality. But because an object is being changed which are immutable in nature, so child won't be notified of the changes unless,

    • it updates config object reference like,

      this.config = new Config()
      
    • or uses onPush change detection strategy and calls the following function to notify the child about the change.

      changeDetectorRef.markForCheck()
      

but when I try changeDetectorRef.markForCheck() child's ngOnChanges life cycle hook is never triggered.

Can anyone explain the reason behind both of these strange issues. I have replicated both issues in this plunker for reference.

Saif
  • 1,745
  • 5
  • 23
  • 46

1 Answers1

2

Basically, Angular only does a 'dirty' check. Meaning complex objects are not properly checked.

Here is a link to someone better explaining it: https://stackoverflow.com/a/34799257/9097714

Another solution is to create an EventEmitter which you can bind through and manually tell the parent <-> child that a change has happened.

For example:

@Output() onChange: EventEmitter<any> = new EventEmitter<any>();

public triggerChanges() {
   this.onChange.emit()
}

Then in the template file:

<app-my-component (onChange)="event"></app-my-component>

Hope this helps!

Edit: https://plnkr.co/edit/9V3vS8S8LSESHNqKSay8?p=preview Here I added ngDoCheck, the only downside is you will have to compare if the value has really be re-assigned.

Jmsdb
  • 515
  • 3
  • 9
  • what I am asking is even when I am manually telling the change detector using markForCheck that I have updated something, it still never fires ngOnChanges of the child. In support of this argument you can refer to the plunkr. Also config object is always in sync between the two its just ngOnChanges which never gets fired. – Saif Mar 05 '18 at 11:09
  • @SaifUllah Unfortunately mark for check doesn't always fire the lifecycle hook, what it does is fire a change detection cycle. In this cycle it compares the objectA <=> objectB. Because the object is complex, it cannot properly do the comparison and then sees 'no changes', thus the lifecycle hook not being called. That's why you much use the other hooks mentioned here: https://stackoverflow.com/a/34799257/9097714 or manually do something with a EventEmitter – Jmsdb Mar 05 '18 at 11:39
  • as i want to propagate the change form parent to child so event emitter won't be an option here. that means my only chance is to use a service or update the object reference by re initializing it. – Saif Mar 05 '18 at 13:35
  • another thing to note is that, config of the child does gets updated, doesn't that mean that change detector did manage to detect this change. – Saif Mar 05 '18 at 13:36
  • No its more than the change detector cannot detect such a change. For example: public numberA = 0. Then, numberA = 1, followed by .markForCheck(). The cycle will compare the old and new value numberA !== numberB ? fire ngOnChanges : nothing happens. The problem is complex objects are hard to compare, so they are not seen, thus the lifecycle hook not firing even if a change is made. Another trick which can be used (found in the link I sent), is to re-assign the object. Or, object = Object.assign({}, object); – Jmsdb Mar 05 '18 at 14:38
  • One last thing, and I am not sure if it will work. You can try: ChangeDetectorRef.detectChanges() instead of markForCheck(). And, see if that forces it. – Jmsdb Mar 05 '18 at 14:46
  • three things here, 1. you can only emit via EventEmitter from child -> parent not in the opposite direction, which is what i want. 2. I have already tried detectChanges() which doesn't work. 3. I am already using the re assigning technique as you can see in the plunkr, but the config object in real life is actually quite complex, so i don't like the idea of re assigning it every time. – Saif Mar 06 '18 at 06:02