0

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/

DaveJ
  • 2,357
  • 6
  • 28
  • 35

2 Answers2

3

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(){});
      }
   };
});
Ben Lesh
  • 107,825
  • 47
  • 247
  • 232
  • Thanks, this works but it needs 2 clicks of the button before the target property is updated. See my JSFiddle here: http://jsfiddle.net/kMybm/10/ – DaveJ Oct 24 '12 at 13:55
  • That's really strange. Seems like a bug to me. If you call $apply() on the $scope, it fixes it... but there is a JavaScript error that says $apply is already in progress... – Ben Lesh Oct 24 '12 at 14:28
  • I've added a hack to fix that issue, using a $watch will force a digest, but I'm not sure why this is happening still... Like I said, seems like a bug. – Ben Lesh Oct 24 '12 at 14:46
  • 1
    Thanks I've added the bug to the github issue tracker: https://github.com/angular/angular.js/issues/1490 – DaveJ Oct 24 '12 at 14:55
  • 1
    @Daveyjoe, FYI the bug appears to be fixed in v1.0.3. – Mark Rajcok Jan 29 '13 at 17:33
2

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/

jpsimons
  • 27,382
  • 3
  • 35
  • 45
  • I don't think that would work because it doesn't expose the model outside of directives scope. Perhaps you could edit my JSFiddle to show me exactly what you mean: http://jsfiddle.net/kMybm/3/. – DaveJ Oct 25 '12 at 20:09
  • Answers in an edit. This is a very good question, actually exposes some of Angular's shortcomings. – jpsimons Oct 29 '12 at 02:37
  • Maybe someone should file a feature request for "adding an optional scope parameter to NgModelController.setViewValue." – jpsimons Oct 29 '12 at 02:46