13

I've got an Angular 2 Controller which looks like this:

@Component({
  selector: 'my-component',
  template: '<div>The value is: {{ value }}</div>',
})
class MyComponent implements OnInit {
  @Input()
  value: string;

  @Output
  valueChange = new EventEmitter<number>();

  ngOnInit() {
    this.valueChange.subscribe(value => {
      console.log('new value:', value); // <-- does not get triggered
    });
  }
}

But when the value of value changes from a template binding:

<my-component [value]="someValue" /> <!-- valueChange not triggered! -->

The valueChange event isn't triggered so, even though the template correctly updates and shows the new value, the component doesn't know it's been changed.

How can I detect when input attributes on my controller are changed?

David Wolever
  • 148,955
  • 89
  • 346
  • 502

3 Answers3

17

In my opinion, Output is for your component to emit event to others so they would update their view if necessary, it should only be used for changes made internally that need to be broadcast out (hence the name Output). Triggering an Output event on Input changes may cause unexpected behavior (as now that valueChange is triggered for all the changes both inside and outside, and not really an Output anymore).

In your case, if you want to do stuff when your value changes, you can make it a setter:

private _value: string;
get value(): string {
  return this._value;
}

@Input('value')
set value(val: string) {
  this._value = val;
  console.log('new value:', value); // <-- do your logic here!
}
Per Hornshøj-Schierbeck
  • 15,097
  • 21
  • 80
  • 101
Harry Ninh
  • 16,288
  • 5
  • 60
  • 54
7

I've used setters for detecting when input variables have been modified. You have to use the inputs key in the @Component decorator instead of the @Input declaration to get this to work, like so:

@Component({
  selector: 'my-component',
  template: '<div>The value is: {{ value }}</div>',
  inputs: ['value']
})
class MyComponent {
  private _value: string;

  @Output
  valueChange = new EventEmitter<number>();

  set value(value) {
    this._value = value;
    console.log('new value:', value);
    this.valueChange.emit(value);
  }

  get value(value) {
    return this._value;
  }
}

Also FYI, the class method is ngOnInit instead of onInit.

Joey Robert
  • 7,336
  • 7
  • 34
  • 31
  • 1
    You can use the inputs property on the \@Component decorator *or* you can use the \@Input decorator on the set property. They both currently work, and the \@Input decorator seems to be the current preferred way – Per Hornshøj-Schierbeck Feb 06 '17 at 12:43
  • A get accessor should and cannot have a value, you may want to update your get method to `get value() {}` I think your `@output` may also need to be updated to `@Output()` – Hlawuleka MAS Nov 06 '17 at 09:09
3

I've been able to work around the issue by implementing from OnChanges:

ngOnChanges(changes) {
  for (let key in changes) {
    if (this[key + 'Change'])
      this[key + 'Change'].emit(changes[key].currentValue);
  }
}

Which will automatically trigger the Change event whenever Angular detects a change… but is there any way to do this natively?

David Wolever
  • 148,955
  • 89
  • 346
  • 502