7

I'm making a game in Angular. Each player object has an x and a y property. Whenever the player moves, I want to start a timer that cycles through a couple background positions in the sprite sheet.

I thought I would do this with a directive. The problem is that directives normally only let you set one expression to watch:

// "test" directive
module.directive("test", function() {
  return function(scope, element, attrs) {
    scope.$watch(attrs.test, function(value) {
      // do something when it changes
    })
  }
})

// my template
<div test="name"/>

The nice thing about this approach, is the test directive doesn't have to assume the scope has any particular property. You're telling it what to use when you use the directive.

The problem is that in my case I need to kick something off if either x OR y changes. How can I do this?

<div test="player.x, player.y"/>
<div test="player.x" test-two="player.y"/>

Is there a best way to do this that you can think of? Basically I want to make a directive that does something on a timer if any of several properties change.

Sean Clark Hess
  • 15,859
  • 12
  • 52
  • 100
  • Note that I do know you can pass a function into scope.$watch, and return something custom, but this question is more about how I TELL the directive what to do in the function when I bind it. – Sean Clark Hess Nov 11 '12 at 04:43

4 Answers4

14

The easiest and most readable solution in my opinion is to use two attributes and simply set up two watches:

// "test" directive
module.directive("test", function() {
  return function(scope, element, attrs) {
    var doStuff = function() {
      console.log(attrs.test);
      console.log(attrs.testTwo);
    }
    scope.$watch(attrs.test, doStuff);
    scope.$watch(attrs.testTwo, doStuff);

  }
})

// my template
<div test test="player1.x" test-two="player1.y" />
Pete BD
  • 10,151
  • 3
  • 31
  • 30
  • I like this syntax the best too, but what if I wanted to make it so this directive could be used on things that don't have an x or a y? All it does is activate when one or more properties change, but the user gets to decide? – Sean Clark Hess Nov 12 '12 at 14:30
  • How would you see the user specifying which attributes to watch? – Pete BD Nov 13 '12 at 18:02
  • I guess that's what I'm asking. How would you do what you recommend without forcing to ONLY work with x and y. With any two properties? or a single property, if the user wanted instead? See my comment on eventOn's answer. – Sean Clark Hess Nov 13 '12 at 22:12
  • 2
    there's now `watchGroup` you can squash the watches together `scope.$watch([attrs.test, attrs.testTwo], doStuff);` – quetzalcoatl Aug 28 '14 at 11:40
7

I would try to use a function in the $watch function.

Here is the plunker

var app = angular.module('plunker', [])
.directive('myDir',function(){
  return {
    restrict:'E',
    template:'<span>X:{{x}}, Y:{{y}}</span>',
    link:function(scope, elm, attrs){
      scope.$watch(function (){
        var location = {};
        location.x = attrs.x;
        location.y = attrs.y;
        return location;
      }, function (newVal,oldVal,scope){
        console.log('change !');
        scope.x = newVal.x;
        scope.y = newVal.y;
      }, true);
    }
  };
});

app.controller('MainCtrl', function($scope) {

});





 <div>X: <input type='text' ng-model='x'/></div>
  <div>Y: <input type='text' ng-model='y'/></div>
  <my-dir x='{{x}}' y='{{y}}'></my-dir>
maxisam
  • 21,975
  • 9
  • 75
  • 84
  • Why store location inside the first param function of $watch? – Pete BD Nov 13 '12 at 18:02
  • then you can monitor the location object. – maxisam Nov 13 '12 at 19:13
  • This is the way to use one watch to monitor more than 1 variable. I didn't say it is faster. However, I think it did save you one watch. You might gain some benefit from that. BTW, this is how the creators of AngularJS do it, I saw one core member use this way somewhere. – maxisam Nov 14 '12 at 16:59
  • IF you are going to do this then why bother storing the variable... scope.$watch(function (){ return { x: attrs.x, y: attrs.y}; }, ..., true); Apart from this, you are now relying on deep equality. I would still go with two watches that call the same function on a change. – Pete BD Nov 15 '12 at 17:28
  • well, I think it is the same. Location is a local variable. You just make it anonymous. What do you mean by "deep equality" ? – maxisam Nov 15 '12 at 17:40
  • The true at the end of the call to $watch is telling AngularJS to use angular.equals rather than === to test whether the value is the same as before. – Pete BD Nov 16 '12 at 19:39
  • From doc, that means compare object for equality rather than for reference. Since I use object here, I need to compare value not reference. if you use watch twice, you don't need to set it true, because it not an object. – maxisam Nov 16 '12 at 22:04
  • There's no issue with storing location, if it makes it more readable. The variable itself will die every time the function is resolved. It's a non-issue. However, having an extra $watch, is far more intensive. – Oddman Feb 17 '14 at 16:56
1

There is some work arounds for this

Watch multiple $scope attributes

https://groups.google.com/forum/?fromgroups=#!topic/angular/yInmKjlrjzI

Community
  • 1
  • 1
  • What about `
    `? The problem is you have to jump over and use `attrs.$observe` instead of `scope.$watch`. It seems weird to have to change which function I use depending on how it's used
    – Sean Clark Hess Nov 11 '12 at 05:04
1
scope.$watch(function () {
  return [attrs.test, attrs.test-two];
}, function(value) {
      // do something when it changes
}, true);

see this link

you can also use $watchGroup - see this link

Community
  • 1
  • 1
Omid.Hanjani
  • 1,444
  • 2
  • 20
  • 29