21

I have a controller that has a counter that changes from time to time.
That counter is tied to an attribute of a directive and read inside the link function of that directive.

How can I have a directive run a function each time that attr value changes?

Thanks.

Francisc
  • 77,430
  • 63
  • 180
  • 276

5 Answers5

41

I am using this aproach:

.directive('mydirective',
 [function (){
    return {
      ...
      scope: {
        id: '@',
      },
      controller: function ($scope, $element, $attrs) {
         $attrs.$observe('id', function(passedId) {
           /// do what is needed with passedId
         });
    ...

And the directive used and id passed like this:

<div mydirective id="{{someprop.id}}" />
Radim Köhler
  • 122,561
  • 47
  • 239
  • 335
  • 1
    Thanks, Radim. [extra chars] – Francisc Dec 05 '13 at 15:13
  • 2
    @Radim Kôhler From Mastering Web Application Dev with AngularJS book, interpolation is already the equivalent of `$observe`, you might not need to call `$observe` in your controller. – Mik378 Dec 05 '13 at 15:14
  • 1
    @Mik378 I would say, that from what I do experience... the observe is needed. Because if passed id is part of a promise... and directive uses it for further processing (e.g. loading data as another promise) you have to observe... to make the passed value in sync. That's how I see it now, with 1.2. Other words, if the passed value is evaluated in the moment of passing... ok. if it is part of a promise... – Radim Köhler Dec 05 '13 at 15:18
  • @Radim Köhler Maybe needed in the `controller` function, but not in the `link` function. I never tested an interpolated attribute inside a `controller` function yet. – Mik378 Dec 05 '13 at 15:20
25

Inside your corresponding link function: (assuming your attribute is called counter and your scope variable is: scope)

scope.$watch(attrs.counter, function (newTime) {
                    //do something with the new Time
});

Other way, surely more efficient way:

Interpolating the attribute

Inside your directive, set the scope property as following (it will be isolated so):

scope: { counter: '@'}

The counter would automatically be observed providing the current value as long as the link function is called.

'@' better than '=' here since you I suppose you don't set the counter to a new value in your directive, meaning you just read it. Indeed, = is more useful for two-way data binding but you might not need it.

Mik378
  • 21,881
  • 15
  • 82
  • 180
12

Use attrs.$observe(key, fn). More info at $compile.directive.Attributes

attrs.$observe('counter',function(counter){

});

Related answer: https://stackoverflow.com/a/14907826/2874153

$observe() is a method on the Attributes object, and as such, it can only be used to observe/watch the value change of a DOM attribute. It is only used/called inside directives. Use $observe when you need to observe/watch a DOM attribute that contains interpolation (i.e., {{}}'s). E.g., attr1="Name: {{name}}", then in a directive: attrs.$observe('attr1', ...). (If you try scope.$watch(attrs.attr1, ...) it won't work because of the {{}}s -- you'll get undefined.) Use $watch for everything else.

Community
  • 1
  • 1
pdorgambide
  • 1,787
  • 19
  • 33
3

If you can, change the directive to isolated scope and use the = definition. This will set up a two-way binding for the scope property:

app.directive('myDirective', function() {

    return {
        scope: { counter: '=' }
    }
});
Davin Tryon
  • 66,517
  • 15
  • 143
  • 132
3

I had to implement this myself and found a solution! Unfortunately, when I tried .$watch, errors were just spewing out on the console. So I used $observe instead.

Here's my solution:

angular.module('app').directive('aDate', [
    function() {
        return {
            template: '<span>{{date}}</span>',
            restrict: 'E',
            replace: true,
            link: function(scope, element, attrs) {
                attrs.$observe('date', function (val) {
                    var d = new Date(val);
                    element.text(d);
                });
            }
        };
    }
]);

The above code changes the scoped date when the date attribute of the element changes!

Cheers!

Justin Cuaresma
  • 459
  • 5
  • 7