11

Referring to Angular2 documentation file here: [https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#parent-to-child-setter] (Parent to child setter), I have the following child component code:

import { Component, Input } from '@angular/core';
@Component({
  selector: 'name-child',
  template: '<h3>"{{name}}"</h3>'
})
export class NameChildComponent {
  private _name = '';
  @Input()
  set name(name: string) {
    console.log("name change");
    this._name =  name;
  }
  get name(): string { return this._name; }
}

And the parent is simple:

import { Component } from '@angular/core';
@Component({
  selector: 'name-parent',
  template: `
  <h2>Parent </h2>
  <name-child [name]="name"></name-child>
  `
})
export class NameParentComponent {
   // somewhere on ngOnInit, set name without changing value
    ngOnInit() { 

        // get pending paged only and so on
        setTimeout(() => {
            this.name = "a";
            setTimeout(() => {
                this.name = "a";
                setTimeout(() => {
                    this.name = "a";
                },1000);

            },1000);

        },1000);
   }
}

You'd expect the console to log out "name change" three times in 3 seconds. It doesn't, it logs it once, and ignores subsequent sets (this happens only if the value does not change). How can I make the input pick up the set event, regardless of whether value changed or not?

Ayyash
  • 4,257
  • 9
  • 39
  • 58
  • you don't change the Input, you send the same value over and over again. – Avi Dec 16 '16 at 19:52
  • 1
    I know, so why isnt the "setter" working? – Ayyash Dec 16 '16 at 19:53
  • 2
    I guess it checks if the values has changed, since not, nothing happens – Avi Dec 16 '16 at 19:54
  • 1
    That's not how it behaves with normal setters and getters, only when it is attached to a child component, I wish there was a way to pass that behavior to the child somehow, I need to catch the set event, even if the value doesnt change – Ayyash Dec 16 '16 at 19:56
  • it doesn't make any sense. if the input has not changed, the listener wont triggers. maybe you should pass another flag or something as input. – Avi Dec 16 '16 at 20:08

4 Answers4

9

I see two ways to solve it:

1) Use immutable value

setTimeout(() => {
  this.name = new String("a");
    setTimeout(() => {
      this.name =  new String("a");
      setTimeout(() => {
       this.name =  new String("a");
    }, 1000);
  }, 1000);
}, 1000);

2) Change input property directly

@ViewChild(NameChildComponent) child: NameChildComponent;

setTimeout(() => {
  this.child.name = "a";
  setTimeout(() => {
    this.child.name = "a";
    setTimeout(() => {
      this.child.name = "a";
    }, 1000);
  }, 1000);
}, 1000);
yurzui
  • 205,937
  • 32
  • 433
  • 399
  • new String() worked, so sad it's the way to go. The second way is a bit too obtrusive if you need to reuse the component multiple times, it needs to be seamless. The other way was to simply reset the string before going into a process where it might be set again. Sad sad story. – Ayyash Dec 17 '16 at 07:08
4

The article you give explicitly says

Intercept input property changes with a setter

when what you are trying to achieve is not about tracking changes at all. It is about sending an event from parent to child and this is where RX Subject (any of 4 of them) or Angular2 EventEmitter is really good.

You can create one of them in the parent and pass it to the child. Then, after you subscribe to it, you can track all the events regardless of the value.

smnbbrv
  • 23,502
  • 9
  • 78
  • 109
  • Understood, so what should I do if I just want to catch property setting? – Ayyash Dec 17 '16 at 07:04
  • There are many things I want. But it does not mean that it is good and it does not mean it is right. Catching the changes in a setter is a really not an obvious way of passing events. But of course you are the boss in your application – smnbbrv Dec 17 '16 at 08:43
  • That's the point, I'm not trying to catch changes, only trigger an event when the prototype is set – Ayyash Dec 17 '16 at 08:46
  • And that's the point: angular is not setting it if the value is the same because angular does it for changes only. This is done for performance reasons – smnbbrv Dec 17 '16 at 08:49
  • It works on the level of typescript, so why are they blocking it between parent-child components? I know there must be a reason, and it is quite a feature to ask for it, anyway, setting new String() triggers the change, though is not optimal – Ayyash Dec 17 '16 at 08:51
  • 1
    if Angular was passing it, it triggered an unnecessary child component `change` which means it is going to be rerendered. This is done, again, for the sake of performance. That's why angular does not set up the property. The way you picked with the `new String()` is an ugly hack because you don't pass the string but pass an object `String` which when compared in JS `new String('1') === new String('1')` gives `false` just because it is another object when `'1' === '1'` is `true`. This is the only reason why it works. – smnbbrv Dec 17 '16 at 09:26
1

Not positive if using the input setter is a strict requirement, but have you tried using OnChanges?

import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
@Component({
  selector: 'name-child',
  template: '<h3>"{{name}}"</h3>'
})
export class NameChildComponent implements OnChanges {
  @Input() name;

  ngOnChanges(changes: SimpleChanges) {
    if (changes['name']) {
      console.log(`Is First Change: ${changes['name'].isFirstChange}`)
      console.log(`name change from ${changes['name'].previousValue} to ${changes['name'].currentValue}`);
    }
  }

}
wickdninja
  • 949
  • 1
  • 10
  • 15
  • i don't know if your angular version changed from mine but isFirstChange is a function so you have to call isFirstChange() – Yetispapa Aug 07 '20 at 11:54
0

While yurzui's answer explains it enough, but if it's an Object you can use

let newObj = JSON.parse(JSON.stringify(oldObj))

it will trigger the setter event

Naman Sharma
  • 283
  • 3
  • 2