DaveJ
DaveJ

Reputation: 2427

Scoping issue when setting ngModel from a directive

I have a directive which looks something like:

var myApp = angular.module('myApp',[])
    .directive("test", function() {
      return {
        template: '<button ng-click="setValue()">Set value</button>',
        require: 'ngModel',
        link: function(scope, iElement, iAttrs, ngModel) {
          scope.setValue = function(){
            ngModel.$setViewValue(iAttrs.setTo);
          }
        }
      };
    });

The problem is that if I use this directive multiple times in a page then setValue only gets called on the last declared directive. The obvious solution is to isolate the scope using scope: {} but then the ngModel isn't accessible outside the directive. ​

Here is a JSFiddle of my code: http://jsfiddle.net/kMybm/3/

Upvotes: 0

Views: 2684

Answers (2)

jpsimons
jpsimons

Reputation: 28100

All you need to do is add scope: true to your directive hash. That makes a new inheriting child scope for each instance of your directive, instead of continually overwriting "setValue" on whatever scope is already in play.

And you're right about isolate scope. My advice to newbies is just don't use it ever.

Response to comment:

I understand the question better now. When you set a value via an expression, it sets it in the most immediate scope. So what people typically do with Angular is they read and mutate values instead of overwriting values. This entails containing things in some structure like an Object or Array.

See updated fiddle:

http://jsfiddle.net/kMybm/20/

("foo" would normally go in a controller hooked up via ngController.)

Another option, if you really want to do it "scopeless", is to not use ng-click and just handle click yourself.

http://jsfiddle.net/WnU6z/8/

Upvotes: 2

Ben Lesh
Ben Lesh

Reputation: 108491

For this scenario ngModel probably isn't the right solution. That's mostly for binding values to forms to doing things like marking them dirty and validation...

Here you could just use a two way binding from an isolated scope, like so:

app.directive('test', function() {
   return {
      restrict: 'E',
      scope: { 
         target: '=target',
         setTo: '@setTo'
      },
      template: '<button ng-click="setValue()">Set value</button>',
      controller: function($scope) {
          $scope.setValue = function() {
              $scope.target = $scope.setTo;
          };
          //HACK: to get rid of strange behavior mentioned in comments
          $scope.$watch('target',function(){});
      }
   };
});

Upvotes: 3

Related Questions