Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS change order of which directives are compiled

As far as I've noticed Angular compiles things on a first come, first-serve basis which is kind of tricky. I made a directive which wraps some elements around and I want to have a link property which looks for stuff in the content.

For a concrete use-case: I am making a input label directive which looks inside the content for the first input and adds a randomly generated id to the input and a for attribute to the label

Here's the code:

// Find the first element with the attribute ng-label-target or the first input and links a label to it
app.directive('ngLabel', function () {
  return {
    restrict: 'E',
    replace: true,
    transclude: true,
    scope: {
      label: '@',
    },
    template: '<span class="ng-label">' +
                '<label class="ng-label-text">{{label}}</label>' +
                '<span class="ng-label-content" ng-transclude></span>' +
              '</span>',
    link: function (scope, element, attrs) {
      scope.id = scope.id || 'random-id-' + Math.floor(Math.random() * 90000000);
      angular.element(element[0].querySelector('.ng-label-text')).
        attr({for:scope.id});

      var target = angular.element(element[0].querySelector('[ng-label-target]'));
      if (!target || target.length == 0) {
        target = angular.element(element[0].querySelectorAll('input,select,textarea')[0]);
      }
      target.attr({id:scope.id});
    },
  };
});

Example Usage:

<ng-label label="Text:">
   <input type="text" ng-model="page.textColor" size="5" maxlength="7" placeholder="e.g. #000" required />
   <input ng-label-target type="color" ng-model="page.textColor" required />
</ng-label>

This by itself works like a charm.

However now I want to auto-generate several inputs and have the label point to the first one. The issue is that when I do a ng-repeat inside my ng-label then ng-repeat code gets generated after my directive was processed so nothing actually gets found.

Therefore my question is: is there a way in which you can specify to angular to evaluate nested directives inside out instead of the other-way around?

Or, is there a batter way of doing this than I'm currently doing?

I made a fiddle to exemplify the order in which stuff gets evaluated. You see that the outer-directive has a smaller or equal value than it's contents (can't go lower than microseconds so I had to do a bunch of repeats):

http://jsfiddle.net/YLM9P/

like image 206
Stefan Avatar asked Dec 08 '13 18:12

Stefan


2 Answers

This problem, like many others in Angular, can be solved by creating an additional directive:

app.directive('myLabelTarget', function() {
 return {
   require: '^myLabel',
   link: function(scope, element, attrs, myLabelCtrl) {
     myLabelCtrl.doIfFirst(function(id) {
       attrs.$set('id', id);  
     });
   }
 };
});

With the require attribute, you can gain access to the controller of the myLabel directive higher up in the DOM.

See this plnkr for a working example.

like image 21
Pieter Herroelen Avatar answered Oct 21 '22 10:10

Pieter Herroelen


From the angular docs:

PRIORITY When there are multiple directives defined on a single DOM element, sometimes it is necessary to specify the order in which the directives are applied. The priority is used to sort the directives before their compile functions get called. Priority is defined as a number. 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. The order of directives with the same priority is undefined. The default priority is 0.

TERMINAL If set to true then the current priority will be the last set of directives which will execute (any directives at the current priority will still execute as the order of execution on same priority is undefined).

So for your problem, I believe setting the terminal property to true will solve your issue.

app.directive('ngLabel', function () {
  return {
    restrict: 'E',
    replace: true,
    transclude: true,
    ....
    ....
    terminal: true
});
like image 55
NicolasMoise Avatar answered Oct 21 '22 10:10

NicolasMoise