0

I am writing an AngularJS directive and would like to bind the ng-model value to an array of objects in the controller. The $render in the directive link I would expect to be fired when anything in the array is changed. In fact, $render is only fired when the reference to the array is changed. Why is this and is this the intended behavior for ngModelController?

Code below demonstrates this behavior and also in this fiddle: http://jsfiddle.net/HandyManDan/RCctF/

<div ng-app="tryApp">
    <div ng-controller="MyCtrl">
        <div> Bound in controller: {{modelVals[0].val}}</div>
        <div> Bound in directive: <span my-dir="" ng-model="modelVals"></span></div>
        <button ng-click="lftBtn()">Mod deep</button>
        <button ng-click="rtBtn()">Mod ref</button>        
    </div>
</div>

myApp.controller('MyCtrl', function($scope) {
    $scope.modelVals = [{val:'val init'}];
    $scope.lftBtn = function() {
        $scope.modelVals[0].val = 'val change deep';
    };

    $scope.rtBtn = function() {
        $scope.modelVals = [{val:'val change ref'}];
    };

});

myApp.directive('myDir', function() {
    return {
        restrict: 'A',
        priority: 1,
        require: 'ngModel',
        link: function(scope, element, attr, ngModel) {
            ngModel.$render = function() {
                element.find('*').remove();
                var el = angular.element("<span>" + ngModel.$modelValue[0].val + "</span>)");
                element.append(el);
            };
        }
    };
});

2 Answers 2

1

Try rewriting your directive like so:

myApp.directive('myDir', function() {
    return {
        restrict: 'A',
        priority: 1,
        scope: {
            'ngModel': '='
        },
        template: "<span>{{ngModel[0].val}}</span>"
    };
});

Demo: http://jsfiddle.net/qwertynl/RCctF/16/

Sign up to request clarification or add additional context in comments.

2 Comments

This made the fiddle work, but not a correct answer. The $render is still not fired.
@HandyManDan I removed the need for the jQuery extras in your code. There is no need for them. Just use the template :-) No need for the render ;-)
1

After digging into angular source, I determined that this is intended behavior. It's documented somewhat here http://docs.angularjs.org/api/ng/directive/select

Note: ngModel compares by reference, not value. This is important when binding to an array of objects.

ngModelController sets up a watcher for the value bound to ng-model. The watch when fired subsequently calls $render. The watcher takes this form

$watch(watchExpression, [listener]);

This form of watch only watches for reference changes only.

To implement this functionality where the directive get an event when the object value changes, one would need to set up their own watch on the ng-model bound value using this form:

$watch(watchExpression, [listener], [objectEquality]);

Setting objectEquality to true indicates object value compare.

Using $watch rather than $render unfortunately bypasses the formatter/validator chain of ngModelController.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.