How to lazy load scripts in AngularJS app?

Some time ago I was having a problem with JS loading from partials in an AngularJS app and today I decided to share my solution with you. The problem is that when web page is loaded, javascript libs in partials are not being loaded. You have to force them to load. You can do this with some imagination, a directive and some jQuery (the last one is optional). 🙂

app.directive('script', [function() {
  function load_script(src, identifier) {		
    var s = document.createElement('script');
    s.type = 'text/javascript';
		
    if(identifier !== undefined) {
      // if element with some identifier exists, remove it                         
      jQuery('script[data-identifier="' + identifier + '"]').remove(); 
      // add an identifier to the new script object so we can remove it if needed
      s.setAttribute('data-identifier', identifier);
    }	
		     
    if(src !== undefined) {		
      s.src = src;
    }
    else {
      var code = elem.text();
      s.text = code;
    }

    document.body.appendChild(s);
  }

  return {
    restrict: 'E',
    scope: false,
    link: function(scope, elem, attr) {
      if (attr.type=='text/javascript-lazy') {
        load_script(elem.attr('src'), elem.attr('data-identifier'));
        elem.remove();
      }
    }
  };
}]);

When partial is loaded the directive happens for each sript object in that partial. If script is of type text/javascript-lazy, load_script function is called. This function creates new element of type text/javascript and adds src or code of lazy element to the new element. All elements with specific (custom) identifier are then removed from DOM and new element is appended to body. When appended to body, script gets forcefully loaded.

How to do detect when ngRepeat is finished and after that do something?

Today I am going to show you a simple solution of the problem, presented in the title of this post. You can detect end of ngRepeat with a directive:

app.directive('onLastRepeat', function() {
  return function(scope, element, attrs) {
    if(scope.$last)
      setTimeout(function() {
        scope.$emit('onRepeatLast');
      }, 0);
  }
});

As you can see I firstly check if last item is being processed. If this is true, I set timeout for 0ms. This is an ugly hack, but it works. The problem is that AngularJS has a process named $digest which asynchronously inserts data into template using ngRepeat directive and we don’t know when the process of data insertion is finished. After that we call our function inside of parent scope with $emit and there the magic happens 🙂 (in my case I reseted some widths and heights).

This solution only works if you have one ngRepeat inside of a controller. In other cases you could decide which function to call based on special attributes or something.

Have a nice day! 😉