How to Watch Directive's Directive ng-model

12,432

As @shaunhusain mentioned you have to use the ngModelController to interact with ngModel. On the ngModelController you can set up a watch on the $modelValue and you can change the value in the model by calling $setViewValue. Remeber that to use the ngModelController you need to add a require: "ngModel" to your directive definition object.

When you get a value from outside of angular (like from your database) and you in turn want to use that value to change the model value, you need to wrap that code in a scope.$apply()

app.directive('thisDirective', function($compile, $timeout, $log) {

    return {
        scope: false,
        require: 'ngModel',

        link: function(scope, element, attrs, ngModel) {
            ...

            scope.$watch(
                function(){
                    return ngModel.$modelValue;
                }, function(newValue, oldValue){
                    $log.info('in *thisDirective* model value changed...', newValue, oldValue);
                }, true);

        }, // end link
    } // end return

});

app.directive('childDirective', function($compile, $timeout, $log) {
    return {
        scope: {
            ngModel: '='
        },
        require: 'ngModel',

        link: function(scope, element, attrs, ngModel) {
            ...

            scope.$watch(
                function(){
                    return ngModel.$modelValue;
                }, function(newValue, oldValue){
                    $log.info('in *childDirective* model value changed...', newValue, oldValue);
                }, true);

            // make believe change by server
            setTimeout(function() {
                scope.$apply(function() {
                    ngModel.$setViewValue('set from the server...');
                };
            },5000);


        },

    } // end return
});

relevant jsfiddle http://jsfiddle.net/CnDKN/2/

BUT I don't think this is not really right usage of $setViewValue. According to the docs, that is supposed to be used to update the value that is shown on the UI, not necessarily the model value.

There is actually another way of making this work which I think is more straightforward and easier to use. Just use =attr to set up bi-directional binding of the property you want to use in both thisDirective and in childDirective. And then you can just set up the the ng-model attribute setting in your directive and when you first use it you don't even need to know that it is using ng-model underneath.

Here is the code that shows you what I mean:

app.directive('thisDirective', function($compile, $timeout, $log) {

    return {
        scope: {
            thisval: '='
        },

        link: function(scope, element, attrs) {

            var htmlText = '<input type="text" ng-model="thisval" />' +
                           '<div child-directive childval="thisval"></div>';

            $compile(htmlText)(scope, function(_element, _scope) {
                element.replaceWith(_element);                
            });

            scope.$watch('thisval',function(newVal,oldVal){
                $log.info('in *thisDirective* thisval changed...',
                          newVal, oldVal);
            });


        }, // end link
    } // end return

});

app.directive('childDirective', function($compile, $timeout, $log) {
    return {
        scope: {
            childval: '='
        },

        link: function(scope, element, attrs) {
            var htmlText = '<input type="text" ng-model="childval" />';

            $compile(htmlText)(scope, function(_element, _scope) {
                element.replaceWith(_element);                
            });

            scope.$watch('childval',function(newVal,oldVal){
                $log.info('in *childDirective* childval changed...',
                          newVal, oldVal);
            });

            // make believe change that gets called outside of angular
            setTimeout(function() {
                // need to wrap the setting of values in the scope 
                // inside of an $apply so that a digest cycle will be 
                // started and have all of the watches on the value called
                scope.$apply(function(){
                    scope.childval = "set outside of angular...";
                });
            },5000);

        },

    } // end return
});

Updated jsfiddle example: http://jsfiddle.net/CnDKN/3/

Share:
12,432
Neel
Author by

Neel

Previously know as @blackops_programmer. I know, it was a pretty lame Display name.

Updated on June 08, 2022

Comments

  • Neel
    Neel about 2 years

    I have a directive that uses the parent scope in that view. This directive has a child directive that uses an isolated scope. I am trying to get the parent directive to watch any changes done to the ngModel of the child directive and update its own modal if changes were made. Here is a jsfiddle that probably explains better: http://jsfiddle.net/Alien_time/CnDKN/

    Here is the code:

       <div ng-app="app">
            <div ng-controller="MyController">
    
                <form name="someForm">
                    <div this-directive ng-model="theModel"></div>
                </form>
    
             </div>
        </div>
    

    Javascript:

        var app = angular.module('app', []);
    app.controller('MyController', function() {
    
    });
    
    app.directive('thisDirective', function($compile, $timeout) {
    
        return {
            scope: false,
    
            link: function(scope, element, attrs) {
                var ngModel = attrs.ngModel;
                var htmlText = '<input type="text" ng-model="'+ ngModel + '" />' +
                               '<div child-directive ng-model="'+ ngModel + '"></div>';
    
                $compile(htmlText)(scope, function(_element, _scope) {
                    element.replaceWith(_element);                
                });
    
                // Not sure how to watch changes in childDirective's ngModel ???????
    
            }, // end link
        } // end return
    
    });
    
    app.directive('childDirective', function($compile, $timeout) {
        return {
                scope: {
                    ngModel: '='            
            },  
    
            link: function(scope, element, attrs, ngModel) {
                var htmlText = '<input type="text" ng-model="ngModel" />';
    
                $compile(htmlText)(scope, function(_element, _scope) {
                    element.replaceWith(_element);                
                });   
    
                // Here the directive text field updates after some server side process
                scope.ngModel = scope.dbInsertId;
    
                scope.$watch('dbInsertId', function(newValue, oldValue) {
                    if (newValue)
                        console.log("I see a data change!");  // Delete this later
                        scope.ngModel = scope.imageId;
                }, true);
    
            },
    
        } // end return
    });
    

    In the example, you can see that there is a text input inside a parent directive as well as its child directive. If you type inside each of them, the other model gets updated since they are binded by ngmodel. However, the child directive's text input gets updated after a server connection. When that happens, the text input in the parent directive doesnt get updated. So I think I need to watch the ngModel inside the child directive for any changes. How can I do that? Does it make sense?

  • Neel
    Neel over 10 years
    Excellent Answer! Learnt so much from it! Thank you @JoseM :)
  • Neel
    Neel over 10 years
    Hi JoseM, After your awesome answer and support, I just put together what I learnt so far. I now understand how and when to use the scope.$appy() method. I was struggling with that. I also find it much easier to have an isolated scope, pass the ng-model and use that to watch and update the model. Your second example helped alot with that. One of the biggest challenge was the $apply. What was happening was just setting the $watch updated the ng-model in child directive but the parent directive was not aware of the change. ....
  • Neel
    Neel over 10 years
    ... Thats why the scope in parent didint get updated although they were binded by ngModelController. Since it was an ajax server side update, adding the $apply method updated the scope and the parent directive became aware of this change. It all worked in the end. phew... what a learning curve it was. Being new to $apply, I had a tough time in understanding how and where to use it and there wasnt too much examples on this. Now it all makes sense. Thank you so much for your help... It feels so good once you learn the Angular magic after being lost in wilderness! :)
  • JoseM
    JoseM over 10 years
    @blackops_programmer check out the egghead.io videos at egghead.io/technologies/angularjs to help you understand AngularJS