280

I have an Angular2 component in that component it currently has a bunch fields that have @Input() applied before them to allow binding to that property, i.e.

@Input() allowDay: boolean;

What I would like to do is actually bind to a property with get/set, so that I can do some other logic in the setter, something like the following

_allowDay: boolean;
get allowDay(): boolean {
    return this._allowDay;
}
set allowDay(value: boolean) {
     this._allowDay = value;
     this.updatePeriodTypes();
}

how would I do this in Angular2?

Based on Thierry Templier suggestion I changed it to, but that throws the error Can't bind to 'allowDay' since it isn't a known native property :

//@Input() allowDay: boolean;
_allowDay: boolean;
get allowDay(): boolean {
    return this._allowDay;
}
@Input('allowDay') set allowDay(value: boolean) {
    this._allowDay = value;
    this.updatePeriodTypes();
}
simbabque
  • 53,749
  • 8
  • 73
  • 136
Paul Cavacas
  • 4,194
  • 5
  • 31
  • 60
  • How and where to do you bind to `[allowDay]="....". If the field (setter) name and the property name you want to use for binding are the same, you can omit the parameter for `@Input(...)`. – Günter Zöchbauer Apr 16 '16 at 10:29
  • I would be curious to see how yo set up your unit test if you went the route of using get set as shown in the accepted answer. – Winnemucca Jan 08 '18 at 22:12
  • 1
    Whatever you end up doing make sure to put a breakpoint, or debug statement, or counter inside your setter to make sure it is only firing once as expected. I just found mine was being updated for every change detection run causing horrible performance and quirky behavior. – Simon_Weaver Feb 14 '18 at 23:53

5 Answers5

459

You could set the @Input on the setter directly, as described below:

_allowDay: boolean;
get allowDay(): boolean {
    return this._allowDay;
}
@Input() set allowDay(value: boolean) {
    this._allowDay = value;
    this.updatePeriodTypes();
}

See this Plunkr: https://plnkr.co/edit/6miSutgTe9sfEMCb8N4p?p=preview.

Yuri
  • 4,254
  • 1
  • 29
  • 46
Thierry Templier
  • 198,364
  • 44
  • 396
  • 360
  • 1
    I get the following error Can't bind to 'allowDay' since it isn't a known native property. See updated question for exactly what I changed the code to – Paul Cavacas Apr 15 '16 at 18:07
  • Are you sure? It works for me. I added a plunkr. Perhaps you forgot to add the directive into the `directives` attribute of the component where you want to use it... I updated my answer. – Thierry Templier Apr 16 '16 at 06:57
  • Can these inputs be optional? For example, if it wasn't using get or set I could use @Input() allowDay?: boolean – abyrne85 Dec 07 '17 at 10:44
  • 4
    This is a bad idea because if you use the setter, ngOnChanges doesn't fire. – user2867288 Feb 14 '18 at 15:44
  • 8
    [On angular Fundamentals pages](https://angular.io/guide/component-interaction#intercept-input-property-changes-with-a-setter) – PaulCo Mar 15 '18 at 12:40
  • 33
    *WARNING*: The `setter` will **NOT** be triggered by mutations to values which are passed by reference (i.e. pushing to an array, mutating an object, etc.). You would need to replace the whole value being passed as an `Input` for the `setter` to trigger again. – Nickofthyme Nov 16 '18 at 14:54
  • @user2867288 : Nope, it does trigger `ngOnChanges()` .Refer https://stackblitz.com/edit/angular-pzwusa?file=src%2Fapp%2Fapp.component.html – Shashank Vivek Mar 17 '19 at 13:30
  • Let me clarify my statement. If you call the setter from inside the component, ngOnChanges() does not fire. E.G. `this.allowDay = true` does not fire ngOnChanges(). – user2867288 Mar 20 '19 at 03:04
  • Yes, @ Thierry Templier : `@Input('[var_name'])` will not be recognized by a parent which has, on the HTML this input.It's a string - Angular doesn't read strings... `Can't bind to '[var_name]' since it isn't a known property of ''` – Pedro Ferreira Jun 09 '20 at 02:59
  • ES Lint won't allow this eslint.org/docs/rules/no-underscore-dangle – Sajith Mantharath Feb 07 '22 at 11:37
96

If you are mainly interested in implementing logic to the setter only:

import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';

// [...]

export class MyClass implements OnChanges {
  @Input() allowDay: boolean;

  ngOnChanges(changes: SimpleChanges): void {
    if(changes['allowDay']) {
      this.updatePeriodTypes();
    }
  }
}

The import of SimpleChanges is not needed if it doesn't matter which input property was changed or if you have only one input property.

Angular Doc: OnChanges

otherwise:

private _allowDay: boolean;

@Input() set allowDay(value: boolean) {
  this._allowDay = value;
  this.updatePeriodTypes();
}
get allowDay(): boolean {
  // other logic
  return this._allowDay;
}
Martin Schneider
  • 14,263
  • 7
  • 55
  • 58
  • Just curious, is there any benefit for using ngOnChanges vs not using the set property if you are only interested on a setter logic? – Mese Jun 22 '17 at 08:52
  • 4
    There is no difference in "using ngOnChanges vs not using set"... ;) Joking aside: One benefit is, if your component has multiple `@Input` properties and you want to call a routine when any of them has changed. So less code needed. – Martin Schneider Jun 22 '17 at 14:35
  • Ups, had a typo hehe. But ok, thought it might had more relevance. Thanks for the answer tho :) – Mese Jun 23 '17 at 08:25
  • 2
    @MA-Maddin I suppose you could also set a debounced observable if you were expecting multiple changes all at the same time that would each result in a routine needing to be run. – Simon_Weaver Jan 18 '18 at 23:02
  • ngOnChanges approach is great!! Good answer. If the value being set cannot be private e.g. it's used as binding in the template, The _propertyName setter/private naming convention becomes inconsistent . ngOnChanges gets around this perfectly – Drenai May 15 '18 at 19:32
  • Heads up... if your input type is an Interface, OnChanges does not catch attribute value changes. – dvdmn Oct 13 '22 at 21:20
  • @dvdmn Yes, since the property's reference stays the same in such case. That's by design and the same in every language, I would say. Re-assigning the value will trigger, as also mentioned in [Nickofthyme's comment](https://stackoverflow.com/questions/36653678/angular2-input-to-a-property-with-get-set/40764091?noredirect=1#comment93559361_36653734) – Martin Schneider Oct 14 '22 at 13:16
  • @MartinSchneider, you are right... but if the logic of Change Detection can detect a value change for a boolean type, it is expected behavior that it would detect a "value change" on an attribute. I think using Object.is() is the wrong choice for change detection as it is failing in this case. – dvdmn Oct 14 '22 at 13:59
  • NgOnChanges will get called a lot. Better off using setters if you only need access to that one variable – Sam Alexander Aug 01 '23 at 14:16
13

@Paul Cavacas, I had the same issue and I solved by setting the Input() decorator above the getter.

  @Input('allowDays')
  get in(): any {
    return this._allowDays;
  }

  //@Input('allowDays')
  // not working
  set in(val) {
    console.log('allowDays = '+val);
    this._allowDays = val;
  }

See this plunker: https://plnkr.co/edit/6miSutgTe9sfEMCb8N4p?p=preview

George Kagan
  • 5,913
  • 8
  • 46
  • 50
maxi-code
  • 311
  • 2
  • 8
  • 9
    This bug got me crazy, I finally found that you should define the Input() first (getter or setter but the input decorator must go first) – maxi-code Nov 21 '16 at 20:23
  • 1
    Here's other reference that may help [https://github.com/angular/angular/issues/5477](https://github.com/angular/angular/issues/5477) – maxi-code Nov 21 '16 at 20:23
1

Updated accepted answer to angular 7.0.1 on stackblitz here: https://stackblitz.com/edit/angular-inputsetter?embed=1&file=src/app/app.component.ts

directives are no more in Component decorator options. So I have provided sub directive to app module.

desertnaut
  • 57,590
  • 26
  • 140
  • 166
Luckylooke
  • 4,061
  • 4
  • 36
  • 49
1

In angular v16.1.0-next.3

We can use transform property on inputs. If an input has a transform function, any values set through the template will be passed through the function before being assigned to the directive instance.

The example from above can be rewritten to the following:

@Input({ transform: booleanAttribute }) allowDay = false;

Example

Chellappan வ
  • 23,645
  • 3
  • 29
  • 60