23

I'm making an Angular 2 web app. I have a model that is comprised of a few key properties, and then several other properties that are computed based off those key values.

All of my properties have getter methods. To keep my computed properties in sync with my key properties, the key properties are changed via setter methods which re-evaluate all of the computed properties. Here's a simplified example:

export class Model{
    private _keyValue: number;
    private _computedValue: number;

    getKeyValue(): number{
        return this._keyValue;
    }

    setKeyValue(value: number){
        this._keyValue = value;
        this.evaluateComputedValues(); // This might be time-consuming
    }

    getComputedValue(): number{
        return this._computedValue;
    }
}

This keeps my model consistent: every time one of the key properties is changed, all of the computed properties are re-computed.

Now I need to figure out how to bind my properties to my component views. It seems like I can present the computed property getters using interpolation:

<div>{{model.getComputedValue()}}</div>

However, I'm not sure what the best way to bind my key properties to input fields would be. All of the examples of using two-way binding seem to use ngModel like this:

<input [(ngModel)]='model.property'>

However, that seems geared towards binding to simple properties. I ideally need two-way binding using my separate getter and setter methods (getKeyValue and setKeyValue).

Is there any built-in way to accomplish this in Angular 2?

MWinstead
  • 1,265
  • 6
  • 12
  • 27
  • 4
    Why don't you use TypeScript getters and setters instead of functions? You get the same functionality but can use them as if they were fields. See also http://stackoverflow.com/questions/12827266/get-and-set-in-typescript – Günter Zöchbauer Oct 31 '16 at 11:59
  • @Günter Zöchbauer If I went that route, would binding the property like this [(ngModel)]='keyValue()' work? Will Angular 2 figure out when to use the getter and the setter? – MWinstead Oct 31 '16 at 12:24
  • 3
    Almost. It would look like `[(ngModel)]='keyValue'`. For such getters/setters you don't need `()`. – Günter Zöchbauer Oct 31 '16 at 12:26

2 Answers2

35

You need to use this longer form

<input [ngModel]='model.getProperty()' (ngModelChange)="model.setProperty($event)">

You should be aware that the getXxx() methods will be called every time change detection runs, which can be quite often. Ensure that the getters return the same value (especially for objects the same instance) and ensure the getters don't have side effects, otherwise you'll get "Expression has changed since it was last checked ..." errors.

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • I'm curious, as you say, the get method will be called every time change detection runs. This will also apply when using typescript getters, no? Would it have an huge impact performance wise? I guess not, if you really just return the value or only do minor processing, right? – curiosity Dec 12 '16 at 15:18
  • 2
    If the method doesn't do actual (expensive) work it's not too bad. It's error-prone if the getter/method returns an object like `return { x: y };` because this way a different object instance is returned which breaks change detection. – Günter Zöchbauer Dec 12 '16 at 20:30
  • True, as you said, one has to be careful. Therefore I can understand why it is discouraged. But on the other hand, e.g. when dealing with awkward REST APIs, getters and setters are the way to go, because you might do some conversion at one single place, the model, and not all components using this model. – curiosity Dec 13 '16 at 06:59
  • If you do conversions you should consider pipes. For asyn results bind to observables isimg `| async` otherwise assign the result to a field and bind to that field. A getter tjat actually does some work shoild be avoided. – Günter Zöchbauer Dec 13 '16 at 07:02
  • Question, there is no way you could use `[(ngModel)]='someValue`, right? Because that would mean you would have to provide a Change function, but I have no clue as how to name that change function so it would be called on input change. It's def not going to be `ngModelChange`, right? I was looking at the example at `https://angular.io/guide/template-syntax#two-way-binding---` but this doesn't use an input with ngModel. – Mattijs Dec 01 '17 at 00:06
  • Sorry, but I don't understand your question. Can you please create a new question with the code that demonstrates what you try to accomplish. A wild guess is that you are looking for how to make `[(ngModel)]` work with a custom component - for this your component needs to implement `ControlValueAccessor` – Günter Zöchbauer Dec 01 '17 at 04:16
10

not sure what version of Angular is being used above, but the version I'm using (v4.3.5) allows binding directly to the getter/setter methods of a field using ngModel: in the typescript file:

  get AnnualRevenueFormatted():string{
    return  this.AnnualRevenue !== undefined ?  `$${this.AnnualRevenue.toLocaleString()}`: '$0';
  }

  // strip out all the currency information and apply to Annual Revenue
  set AnnualRevenueFormatted(val: string) {
    const revenueVal = val.replace(/[^\d]/g, '');
    this.AnnualRevenue = parseInt(revenueVal);
  }

and in the template file

<input type="text" class="text-input" [(ngModel)]="accountInfo.AnnualRevenueFormatted"  />
Daryl1976
  • 675
  • 2
  • 8
  • 20