7

I have a div element with ng-bind directive:

<div ng-bind="sometext"></div>

I have a directive that gets an element, checks its value / text and adds a color to the element according to the content. I am using this directive like that:

<div ng-bind="sometext" my-directive></div>

The problem is that on the time the directive is executing, there is no value or text on the div because ng-bind didn't happened yet.
I am getting the text using element.text().
Any idea how to make the text available inside my directive?

Naor
  • 23,465
  • 48
  • 152
  • 268
  • you will need to change the priority of your directive so that it runs after ng-bind. I've never done anything with priorities yet, so I can't give you too much help though. – Matt Greer Jan 11 '14 at 20:24
  • @MattGreer: I thought the solution will be something like that. I saw angularJS code and there is no priority to ng-bind. I need to set less priority then ng-bind because I want my directive to run last.Any idea? – Naor Jan 11 '14 at 20:33
  • I had a similar problem last week which I solved by wrapping `$timeout` around part of my code, so it's evaluated the next digest cycle when the binding has happened. I'm not entirely happy with that solution, but perhaps it suffices for your needs. – towr Jan 11 '14 at 20:36
  • @Naor have you tried adding `priority: 1` to your directive? just as a curiosity, maybe it really is that simple? – Matt Greer Jan 11 '14 at 20:43

3 Answers3

13

Your directive may be running before ngBind has bound it's value - both your directive and ngBind are priority 0 so either could run first, more on that in a moment- but let's look at the ngBind source code to see the root of the problem:

var ngBindDirective = ngDirective(function(scope, element, attr) {
  element.addClass('ng-binding').data('$binding', attr.ngBind);

  scope.$watch(attr.ngBind, function ngBindWatchAction(value) {
    element.text(value == undefined ? '' : value);
  });

});

We see that ngBind doesn't immediately update the DOM, but rather places a watch on the ngBind attribute. So the element won't be updated until that watch is run on the next $digest cycle (which is why a $timeout works).

So one option is to mimic ngBind and place your own watch on it's attribute- then you'll be updated whenever the ngBind result changes:

angular.module('myApp').directive('myDirective', function() { 
    return {
        priority: 1,
        link: function(scope,element,attrs) {
            scope.$watch(attrs.ngBind, function(newvalue) {
              console.log("element ",element.text());
            });           
        }
    };      
});

You'll note I set priority to 1. You do need to be sure that this directive's watch is placed after the ngBind watch in the watch queue. That will ensure the element has been updated by ngBind first.

By default a directive's link function runs post-link, so as the $compile docs note:

Directives with greater numerical priority are compiled first. Pre-link functions are also run in priority order, but post-link functions are run in reverse order.

Therefore, since ngBind is priority 0, anything over 0 will ensure your directive's watch will come after the ngBind watch..

demo fiddle

KayakDave
  • 24,636
  • 3
  • 65
  • 68
  • Good answer ! A few words explaining why a higher priority causes the `$watch` registered in the `postLink` function to fire **after** ngBind's `$watch` would be nice :) – gkalpak Jan 11 '14 at 21:27
  • @ExpertSystem thanks! Good point- I updated with a bit on that. – KayakDave Jan 11 '14 at 21:33
  • Unless the AngularJS team changes the priority of `ng-bind`. I would strongly suggest you don't base your code's behavior on mutating the result of `ng-bind`. – rtcherry Jan 11 '14 at 21:39
  • @rtcherry I get your concern. But it's not really mutating the results of ng-bind, it's just adding a watch that follows theirs. And, of course, Angular could change anything- but it would be tagged as a breaking change- they do publish their priorities (for example making `ngRepeat`s 1000 priority prominent). – KayakDave Jan 11 '14 at 21:50
  • Your code is not mutating the result, but the original question is about reading the text value and then changing color. – rtcherry Jan 11 '14 at 21:55
  • @rtcherry fair enough. I think of this more like chaining- for example adding another parser in the parse chain. But I focused on "Any idea how to make the text available inside my directive?" Overall I think having multiple ways to approach this is great for SO. – KayakDave Jan 11 '14 at 22:01
8

Edit 2

The other option is to use ng-class or ng-style for changing the color of the text. Then you don't have to create a new directive at all.

Original Answer

I would not depend on the ng-bind directive at all... This seems much cleaner in my opinion.

<div ng-bind="someModel" my-directive="someModel"></div>

And then define your directive as...

angular.module('myApp').directive('myDirective', function() { 
    return {
        link: function(scope, element, attrs) {
            scope.$watch(attrs.myDirective, function(newValue, oldValue) {
              // Your Code here...
            });           
        }
    };      
});

This way you can use your directive even if you don't have an ng-bind on the element (for example, if you use curly braces instead).

<div my-directive="someModel">{{someModel}}</div>

Alternatively you can use attrs.$observe(...) (documentation) instead of scope.$watch(...).

<div ng-bind="someModel" my-directive="{{someModel}}"></div>

and

angular.module('myApp').directive('myDirective', function() { 
    return {
        link: function(scope, element, attrs) {
            attrs.$observe('myDirective', function(interpolatedValue) {
              // Your Code here...
            });           
        }
    };      
});

You can find more information about the differences between scope.$watch(...) and attrs.$observe() here.

Edit

Given that your directive is basically mutating the DOM after the ng-bind directive, why not skip the ng-bind all together?

<div my-directive="{{someModel}}"></div>

and

angular.module('myApp').directive('myDirective', function() { 
    return {
        link: function(scope, element, attrs) {
            attrs.$observe('myDirective', function(interpolatedValue) {
              if (interpolatedValue) {
                // Modify interpolatedValue if necessary...
              }
              element.text(interpolatedValue == undefined ? '' : interpolatedValue);
            });           
        }
    };      
});
Community
  • 1
  • 1
rtcherry
  • 4,840
  • 22
  • 27
  • I used watch on an attribute value. This is the most logical thing to do. Thanks a lot! – Naor Jan 11 '14 at 22:40
0

You could use a scope.$watch in you link function and watch for model changes. Each time the value changes ng-bind will fire and your directive will be fired as well, independent to the bg-bind directive and dependent only to the modle itself.

Sorry i cant presnt you with sample code as im on a tablet while answering your question.

Kia Panahi Rad
  • 1,235
  • 2
  • 11
  • 22