7

I have two directives:

// Generated by CoffeeScript 1.6.3
app.directive("focusMe", function() {
  return {
    scope: {
      focus: "=focusMe"
    },
    link: function(scope, element) {
      return scope.$watch("focus", function(value) {
        if (value === true) {
          element[0].focus();
          return scope.focus = false;
        }
      });
    }
  };
});

and:

// Generated by CoffeeScript 1.6.3
app.directive("cleanMe", function() {
  return {
    scope: {
      clean: "=cleanMe"
    },
    link: function(scope, element) {
      return scope.$watch("clean", function(value) {
        if (value === true) {
          element[0].value = "";
          return scope.clean = false;
        }
      });
    }
  };
});

and this input (angularUI):

<input type="text" ng-model="addUserSelected" typeahead="emp.value for emp in allUsers | filter:$viewValue | limitTo:5" typeahead-editable="false" typeahead-on-select="addLine($item.id)" focus-me="usersFocusInput" clean-me="usersCleanInput">

I get this error:

Error: [$compile:multidir] http://errors.angularjs.org/1.2.3/$compile/multidir?p0=cleanMe&p1=focusMe&p…2%20focus-me%3D%22usersFocusInput%22%20clean-me%3D%22usersCleanInput%22%3E

what do I do wrong?

If I remove the clean-me property from the html it works.

Thanks

Noampz
  • 1,185
  • 3
  • 11
  • 19
  • why aren't you just reseting model that `ng-model` is bound to? I suspect you don't need `cleanMe` directive – charlietfl Dec 09 '13 at 13:22
  • cleanMe is no the point, it can be any 2 (or more) directives. clean-me and focus-me is just for the example. – Noampz Dec 09 '13 at 13:29

3 Answers3

8

There is no real need for isolated scopes here. Use a "normal" directive scope and the directive will just inherit from the parent, like this:

// Generated by CoffeeScript 1.6.3
app.directive("focusMe", function() {
  return {
    link: function(scope, element, attrs) {
      return scope.$watch(attrs.focusMe, function(focusMe) {
        if (focusMe.value === true) {
          element[0].focus();
          return scope[attrs.focusMe].value = false;
        }
      });
    }
  };
});

// Generated by CoffeeScript 1.6.3
app.directive("cleanMe", function() {
  return {
    link: function(scope, element, attrs) {
      return scope.$watch(attrs.cleanMe, function(cleanMe) {
        if (cleanMe.value === true) {
          element[0].value = "";
          return scope[attrs.cleanMe].value = false;
        }
      });
    }
  };
});

Ignore this part if you already know how inheritance works, just adding for completeness:

Note that I am using the [attrs.focusMe].value, not just [attrs.focusMe]. The reason is how inheritance works in javascript. These directives are child scopes, so if you try to do scope[attrs.focusMe] = false you will set a local variable in the scope of the directive, i.e. you will not affect the parent scope (the controller where it is used). However, if you make the focusMe model (whatever it is) an object in the parent scope and then change a value on that object, then it will not set a local variable, it will instead update the parent. So:

scope[attrs.focusMe] = false; // Sets a local variable, does not affect the parent
scope[attrs.focusMe].value = false; // Updates focusMe on the parent

Here is a good answer about inheritance if you want an in depth guide: What are the nuances of scope prototypal / prototypical inheritance in AngularJS?

Community
  • 1
  • 1
Erik Honn
  • 7,576
  • 5
  • 33
  • 42
  • 1
    Fixed a error with scope, it should be scope[attrs.focusMe].value not scope.focusMe.value of course. You want the scope variable wpointed out by the focusMe attribute. Also, if the value you feed it is not on the "root", like something.usersFocusInput, then it takes a bit more effort to change it. – Erik Honn Dec 09 '13 at 14:10
6

You have two directives which require isolated scope on the same element which is not allowed.

The reason this is not allowed is because if you have some template {{...}} code inside the directive, then it will be unclear which scope it should take its values from.

Consider instead of isolating scope, using attribute.$observe to watch the cleanMe and focusMe properties and acting on those.

app.directive("focusMe", function() {
  return {
    link: function(scope, element, attributes) {
      attributes.$observe("focusMe", function(value) {
        if (value === true) {
          element[0].focus();
          scope.focus = false;
        }
      });
    }
  };
});

and:

app.directive("cleanMe", function() {
  return {
    link: function(scope, element, attributes) {
      attributes.$observe("cleanMe", function(value) {
        if (value === true) {
          element[0].value = "";
          return scope.clean = false;
        }
      });
    }
  };
});
musically_ut
  • 34,028
  • 8
  • 94
  • 106
  • First yhanks you for your help!! there are 2 problems with this solution for me: first "value" is equal to "usersFocusInput" instead of value of the parameter in the scope $scope.usersFocusInput which should be true/false. second scope.clean is undefined and I want to replace $scope.usersFocusInput back to false. – Noampz Dec 09 '13 at 13:01
  • @Noampz Then you want to use `scope.$parent.watch(attributes['focusMe'], ...)` and set `scope.$parent[attributes['focusMe']] = false`. Similarly for `cleanMe`. Does that work for you? – musically_ut Dec 09 '13 at 13:08
  • Why not just scope.$watch(attributes.focusMe)? – Erik Honn Dec 09 '13 at 13:31
  • Unless directive inheritance is changed from the 1.1 branch to the 1.2 branch that should work just fine, since the directive will inherit from the scope where it is used. – Erik Honn Dec 09 '13 at 13:32
  • @ErikHonn Unless one accidentally defines 'focusMe' on the current scope. Using `$scope.$parent` will obviate that error. Also, while overriding the property, one needs to use `$scope.$parent` lest one should write the properly on the local scope. – musically_ut Dec 09 '13 at 13:43
  • Typo from my side, it should be attributes.focusMe. Defining focusMe on the correct scope won't affect that at all. Also, you should (generally) not use $parent to update the property of a parent, you should use an object. Doing $scope.foo.value = 'something' is better than doing $scope.$parent.value = 'something'. – Erik Honn Dec 09 '13 at 13:47
1

I found the solution finally :)

// Generated by CoffeeScript 1.6.3
app.directive("focusMe", function() {
  return {
    link: function(scope, element, attributes) {
      return scope.$watch(attributes.focusMe, function(value) {
        if (scope[value] === true) {
          element[0].focus();
          return scope[attributes.focusMe] = false;
        }
      });
    }
  };
});

// Generated by CoffeeScript 1.6.3
app.directive("cleanMe", function() {
  return {
    link: function(scope, element, attributes) {
      return scope.$watch(attributes.cleanMe, function(value) {
        if (value === true) {
          element[0].value = "";
          return scope[attributes.cleanMe] = false;
        }
      });
    }
  };
});

In the html usersFocusInput and usersCleanInput are parameters in the scope that is wht I use scope[attributes.focusMe] to get this parameter and change him to false.

<input type="text" ng-model="addUserSelected" typeahead="emp.value for emp in allUsers | filter:$viewValue | limitTo:5" typeahead-editable="false" typeahead-on-select="addLine($item.id)" focus-me="usersFocusInput" clean-me="usersCleanInput">
Noampz
  • 1,185
  • 3
  • 11
  • 19
  • Close, but not quite. :) Note that scope[attributes.focusMe] = false will set a local variable in the directive, it won't update the parent controller. You need to make it an object (or use $parent) for that. See my answer above/below/wherever it happens to be at the moment. – Erik Honn Dec 09 '13 at 14:16