Dynamically add angular attributes to an element from a directive

All we need is an easy explanation of the problem, so here it is.

I’m trying to build a directive that change loading status on buttons for slow ajax calls. Basically the idea is to set an attribute “ng-loading” to a button element, and let the directive add the rest of stuff.

This is the html code:

<button class="btn btn-primary" name="commit" type="submit" ng-loading="signupLoading">
  Submit
</button>

And this is the directive code:

.directive('ngLoading', ['$compile',  function($compile) {
  return {
    restrict: 'A',
    replace: false,
    terminal: true,
    link: function(scope, element, attrs) {
      element.attr('ng-class', '{loading:' + attrs['ngLoading'] +'}');
      element.attr('ng-disabled', attrs['ngLoading']);
      element.append(angular.element('<img class="loading-icon" src="/assets/images/loading-icon.gif"/>'));
      $compile(element.contents())(scope);
    }
  };
}]);

It all looks correct in the rendered HTML, but the attributes added from the directive is not funcioning at all. I can move those attributes to the HTML code and everything works great, but that’s quite some redundant code in many places.

I referenced the post Angular directive to dynamically set attribute(s) on existing DOM elements but it does not solve my problem.

Any comment/suggestion are welcome. Thanks in advance.

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

You don’t need to recompile that directive if all you want is some DOM manipulation, you can add and remove class in regards to the changes of a scope property. You can use $watch instead.

JAVASCRIPT

.directive('ngLoading', function() {
  return function(scope, element, attrs) {
    var img = angular.element('<img class="loading-icon" src="/assets/images/loading-icon.gif"/>');
    element.append(img);
    scope.$watch(attrs.ngLoading, function(isLoading) {
       if(isLoading) {
         img.removeClass('ng-hide');
         element.addClass('loading');
         element.attr('disabled', '');
       } else {
         img.addClass('ng-hide');
         element.removeClass('loading');
         element.removeAttr('disabled');
       }
    });
  };
});

Note: Your code doesn’t work because it compiles the contents of the elements, not the element itself, where you attach the attributes you have implemented.

try $compile(elem)(scope); and it should work properly, but I don’t recommend it because each element with such directive will have to re-compile again.

UPDATE:
before using $compile remove the attribute ‘ngLoading’ to the element to prevent infinite compilation.

elem.removeAttr('ng-loading');
$compile(elem)(scope);

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply