25
votes

I'm trying to write a directive which make use of isolated scope and ngModel directive.

Problem:
When the model is updated in the directive the value of the caller is not getting updated.

HTML:

<test-ng-model ng-model="model" name="myel"></test-ng-model>

Directive:

app.directive(
    'testNgModel', [
    '$timeout',
    '$log',

function ($timeout, $log) {

    function link($scope, $element, attrs, ctrl) {
        var counter1 = 0, counter2 = 0;

        ctrl.$render = function () {
            $element.find('.result').text(JSON.stringify(ctrl.$viewValue))
        }

        $element.find('.one').click(function () {
            if ($scope.$$phase) return;
            $scope.$apply(function () {
                var form = angular.isObject(ctrl.$viewValue) ? ctrl.$viewValue : {};
                form.counter1 = ++counter1;
                ctrl.$setViewValue(form);
            });
        });
        $element.find('.two').click(function () {
            if ($scope.$$phase) return;
            $scope.$apply(function () {
                var form = angular.isObject(ctrl.$viewValue) ? ctrl.$viewValue : {};
                form.counter2 = ++counter2;
                ctrl.$setViewValue(form);
            });
        });

        $scope.$watch(attrs.ngModel, function (current, old) {
            ctrl.$render()
        }, true)
    }

    return {
        require: 'ngModel',
        restrict: 'E',
        link: link,
        //if isolated scope is not set it is working fine
        scope: true,
        template: '<div><input type="button" class="one" value="One"/><input type="button" class="two" value="Two"/><span class="result"></span></div>',
        replace: true
    };

}]);

Demo: Fiddle

If the isolated scope is not set it works fine: fiddle

2
scope: true does not create an isolate scope, it creates a new child scope that prototypically inherits from the parent scope, hence the reason $parent.model works. (An isolate scope is created when we use the scope: { ... } syntax. Here, an new child scope is also created, but it does not prototypically inherit from the parent.) In general, a child scope should be used with ng-model since you are creating a component that needs to interact with other directives (i.e., ng-model). So I suggest you go with your second, working, fiddle. - Mark Rajcok
@MarkRajcok that is not an option for me since the directive is more complex and it adds some custom attributes to the scope which will lead to pollution of the parent scope - Arun P Johny
Ok, use scope: true, but also use an object, not a primitive: <test-ng-model ng-model="someObj.model" ...>. fiddle. - Mark Rajcok

2 Answers

13
votes

As discussed in the comments, it is generally not recommended to use a child scope (scope: true or scope: { ... }) with ng-model. However, since Arun needs to create additional scope properties, scope: true can be used with an object, not a primitive. This leverages prototypical inheritance, so $parent is not neeed:

<test-ng-model ng-model="someObj.model" ...>

fiddle

7
votes

Because you created an isolated scope, ngModel="model" refers to your new isolated scope. If you want to refer to your AppController scope, you should use $parent:

<test-ng-model ng-model="$parent.model" name="myel"></test-ng-model>