5

I have two variables that are related by a function, and the user should be able to change one or the other in an input field which should automatically change the other.

How can I do that, right now I'm just using $watch for both.

Here's some sample code and a fiddle.

JS,

angular.module("test", [])
.controller("MyController", function ($scope) {
    $scope.letter = 'A';
    $scope.number = 1;
    $scope.map = { 'A': 1, 'B': 2, 'C': 3, 'D': 4 };

    $scope.$watch('letter', function(new_val, old_val){
        if(new_val != old_val){
            $scope.number = $scope.map[new_val];
        }
    });
    $scope.$watch('number', function(new_val, old_val){
        ...
    });
});

HTML,

<div ng-app="test">
    <div ng-controller="MyController">
        <input ng-model="letter" />
        <input type="number" ng-model="number" />
    </div>
</div>
1
  • 3
    Seems like a strange thing to do. Possibly look at ng-change too. Commented Feb 25, 2014 at 12:38

1 Answer 1

10

There are a number of ways you can do this, and using $watch is certainly one of them. As mentioned by Matt, you could also use the ng-change directive to fire a method on your controller.

The third way that I would like to offer up, is to make use of ES5 properties, and the Controller 'as' syntax that Angular introduced in 1.2+

If you define your controller as a JS object instead of using an anonymous function, you can add properties and methods to the prototype:

myController = function () {
    this.map = {'A': 1,'B': 2,'C': 3,'D': 4};
    this._letter = 'A';
    this._number = 1;
};

Now we can extract the work you have already done for getting your letter and number values into functions:

myController.prototype.getLetterValue = function (num) {
    for (var key in this.map) {
        if (this.map.hasOwnProperty(key)) {
            if (this.map[key] === num) {
                return key;
            }
        }
    }
};

myController.prototype.getNumberValue = function (letter) {
    return this.map[letter];
};

Lastly, we are going to declare a couple of properties on your controller that encapsulate the desired functionality using Object.defineProperty.

Object.defineProperty(
myController.prototype,
    "letter", {
    get: function () {
        return this._letter;
    },
    set: function (newValue) {
        this._letter = newValue;
        this._number = this.getNumberValue(this._letter);
    },
    enumerable: true,
    configurable: true
});

Object.defineProperty(
myController.prototype,
    "number", {
    get: function () {
        return this._number;
    },
    set: function (newValue) {
        this._number = newValue;
        this._letter = this.getLetterValue(this._number);
    },
    enumerable: true,
    configurable: true
});

Add this controller to your module:

angular.module("test", [])
    .controller("MyController", myController);

And lastly, you just need to modify your binding syntax slightly in order to use the new Controller 'as' syntax. This will allow you to bind directly to properties and methods on your controller instead of having to use $scope

<div ng-app="test">
    <div ng-controller="MyController as ctrl">
        <input ng-model="ctrl.letter" />
        <input type="number" ng-model="ctrl.number" />
    </div>
</div>

Live Demo

Summary

This isn't exactly less code, but does have several advantages.

  • Your controller is decoupled from $scope and $watch making it more portable
  • The controller code is easier to read because all the functionality isn't nested inside an anonymous function
  • The code is a little more forward looking because future versions of Angular will probably eliminate $scope and the $digest loop altogether by using native observables.
Sign up to request clarification or add additional context in comments.

2 Comments

+1 +1 awesome... never thought about using getters and setters here. But that's awesome...
@xcorat - The biggest change from your fiddle to the one I created is that I am using Angular 1.2+ That is a pre-requisite for the Controller 'as' Syntax. Also, ES5 will not work in IE<9, but hopefully that is not a restriction for you.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.