2

I am having issues while triggerin Angular(6)'s onChanges life cycle hooks. While emitting parameters from a compontent to a directive I want to hook on the changes.

The trigger works perfectly on one-dimensional variables, while objects are not triggered correctly. Especially if an partial of an multi-dimensional object is transmitted to a child directive. At least it works, but the OnChange hook only triggers the one-dimensional variable.

Have a closer look on StackBlitz, where I've provided an executable example. Mind to open the console, to see, which life cycle is triggered: https://stackblitz.com/edit/angular-vhaews

app.component.ts

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {
  public paramNumber;
  public paramObject;

  ngOnInit() {
    this.paramObject = {
      x: 10,
      y: 10
    };
    this.paramNumber = 42;
  }
}

app.component.html

<div appCanvas [paramObject]="paramObject" [paramNumber]="paramNumber"></div>

ParamObject X
<input name="paramObject.x" type="number" [(ngModel)]="paramObject.x"/>
<br>
ParamObject Y
<input name="paramObject.y" type="number" [(ngModel)]="paramObject.y"/>
<pre>{{paramObject | json}}</pre>

ParamNumber
<input name="paramNumber" type="number" [(ngModel)]="paramNumber"/>
<pre>{{paramNumber | json}}</pre>

canvas.directive.ts

import { Directive, ElementRef, Input, OnInit, OnChanges } from '@angular/core';

@Directive({
  selector: '[appCanvas]'
})
export class CanvasDirective implements OnInit, OnChanges {

  @Input() private paramObject;
  @Input() private paramNumber;

  ngOnChanges() {
    console.log(`changed object`, this.paramObject);
    console.log(`changed number`, this.paramNumber);
  }

  ngOnInit() {
    console.log(`changed object`, this.paramObject);
    console.log(`changed number`, this.paramNumber);
  }
}

Disclaimer: That problem is part of an bigger issue and I've tried to isolate the main issue. So please do not look to much into details, it's really about the life cycle hook and the multi-dimensional object.

Michael W. Czechowski
  • 3,366
  • 2
  • 23
  • 50
  • I think the input field could be the problem. While updateing only a part of the object, maybe the form is not updated somehow. But it's an hypothesis. – Michael W. Czechowski May 20 '18 at 14:30
  • 4
    That's right, it doesn't. Angular's lifecycle can't tell when you've changed the properties of the object; you need to use a more functional style and *replace* the object with an updated one (e.g. `this.paramObject = { ...this.paramObject, y: newValue }`). – jonrsharpe May 20 '18 at 14:31
  • 1
    Another possible solution is to define an input variable for each property of `paramObject` that you want to monitor (see [this stackblitz](https://stackblitz.com/edit/angular-gdcsgr)). – ConnorsFan May 20 '18 at 14:42
  • @jonrsharpe You mean updating it manually on side of the component? Could you be so kind an serve an quick answer with an example? – Michael W. Czechowski May 20 '18 at 15:09

2 Answers2

1

This is a typical angular scenario when detecting changes in an Object's properties. Take a look at ngDoCheck(), which can be used to put custom change detection logic.

Angular lifecycle will only detect the object reference changes. Because why not? Doing a deep object equals comparison can be a performance hit!

What you can do is -

  1. Replace the object with a new instance. For instance, use Object.assign.
  2. Put custom change detection logic in ngDoCheck() method.
Oorja
  • 139
  • 2
  • 9
  • 1
    Sounds good, but I am missing a bit an concrete example. Where should I replace that object, inside the directive or component? – Michael W. Czechowski May 20 '18 at 15:01
  • And do check was kind of over-triggering, it even detected clicks etc. – Michael W. Czechowski May 20 '18 at 15:02
  • You would place the replace logic in the code that was updating the object. Instead of updating it would replace with new. But I guess you already arrived at a good solution! :) – Oorja May 20 '18 at 16:42
  • At the end I am not super satisfied, because that object has a lot more of properties, but for now it will be okey. If someone got another good solution, I'll be glad! – Michael W. Czechowski May 20 '18 at 22:52
1

Wrapping all suggestions up, it looks like splitting the object properties into individual variables is the best solution:

app.component.html

<input name="canvasParam.x" type="number" [(ngModel)]="canvasParam.x"/>

canvas.directive.ts

private param;

@Input() private paramX;
@Input() private paramY;


ngOnChanges() {
  this.param = {
    x: this.paramX,
    y: this.paramY
  };
  console.log(`changes`, this.param);
}
Michael W. Czechowski
  • 3,366
  • 2
  • 23
  • 50