Question

I am trying to get getter/setter support for ng-model by implementing a directive that will take care of getting and setting the values to/from the view/model. I am almost there, but I end up in infinite $digest loops.

The idea is to set ng-model="$someFieldToStoreInTheScope", and then have the getter/setter directive do the updates between that field and the getter/setter functions.

I use $watch to update the model using the setter expression when the ngModelController updates the field in the scope, and another watch to update that field when the getter expression changes.

Have a look at: http://jsfiddle.net/BDyAs/10/

Html:

<div ng-app="myApp">
<body>
<form name="form">    
    <input type="text" ng-model="$ngModelValue" ng-model-getter-setter="get=getValue();set=setValue($value)"/> {{myDerivedValue}}
</form>
</body>
</div>

JS:

var myApp = angular.module('myApp', []);

myApp.directive(
    {
        'ngModelGetterSetter': function () {
            return {
                require: "ngModel",
                controller: ctrl,
                link:  function(scope, element, attrs, ngModelCtrl)
                {
                    var getterSetterExpression = attrs.ngModelGetterSetter;
                    var tokens = getterSetterExpression.split(";");
                    var getExpression = tokens[0].split("=")[1];
                    var setExpression = tokens[1].split("=")[1];

                    function updateViewValue()
                    {
                        var updateExpression = attrs.ngModel + "=" + getExpression;
                        scope.$eval(updateExpression);
                    }

                    function updateModelValue()
                    {
                        scope.$value = ngModelCtrl.$viewValue;
                        scope.$eval(setExpression);
                    }

                    updateViewValue();    

                    scope.$watch(getExpression, updateViewValue);
                    scope.$watch(attrs.ngModel, updateModelValue);
                }
            };
        }
    });

function ctrl($scope) {
    $scope.getValue = function ()
    {
        return $scope.myValue;
    }

    $scope.setValue = function (val)
    {
        $scope.myValue = val;
        $scope.myDerivedValue = $scope.myValue * 2;
    }

    $scope.setValue(5);

    setInterval(function () { $scope.setValue($scope.getValue() + 1); $scope.$apply(); }, 1000);
}

I have put a setInterval() in my code to modify the model and see if it propagates correctly in the view.

Any idea why there is an infinite digest loop, and how to remove it?

Was it helpful?

Solution

NOTE AngularJs 1.3 now supports Getter/Setter for ng-model. Refer to http://docs.angularjs.org/api/ng/directive/ngModelOptions for more information.


I could break the infinite loop with extra calls to

ngModelCtrl.$setViewValue()

and

ngModelCtrl.$render()

in the event handlers. Not sure if it's the best way to do it though.

See fiddle: http://jsfiddle.net/BDyAs/12/

EDIT:

I improved the code even more in

http://jsfiddle.net/BDyAs/15/

by separating the directive in separate ones for the getter and the setter.

OTHER TIPS

I think the question about breaking the digest loop has been answered. Here is a different, much cleaner approach to the same problem that doesn't involve $watch.

When you don't need to support legacy browsers, use ECMAScript 5 accessors.

Just add a property to your angular controller:

Object.defineProperty(
    $scope, 
    "accessorWrappedMyValue",            
    {
        get : function() { return $scope.myValue; },  
        set : function(newValue) { 
                  $scope.myValue = newValue; 
                  $scope.myDerivedValue = $scope.myValue * 2;
              },
        configurable: true
    });

Now all you need to do is reference accessorWrappedMyValue from ng-model like so:

<input type="text" ng-model="accessorWrappedMyValue" />

Further reading

This blog has a nice introduction to ES5 accessors.

Use this feature matrix to decide whether you can go with ES5 or not. The interesting lines are "Getter / Setter in property initializer" and "Object.defineProperty".

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top