Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / AngularJs

Lazy Loading Directives in AngularJS the Easy Way

3.00/5 (3 votes)
4 Nov 2014CPOL3 min read 38.9K  
Lazy loading directives in AngularJS the easy way

During the past few months, I've been doing a lot of work with AngularJS, and currently I'm working on a single page application which is supposed to be quite big in the end. Since I have the privilege of building it from scratch, I'm taking many client-side performance considerations in mind now, which I think will save me a lot of hard work optimizing in the future.

One of the main problems is HUGE amounts of js files being downloaded to the user's computer. A great way to avoid this is to only download the minimum the user needs and dynamically load more resources in the background, or as the user runs into pages which require a specific feature.

AngularJS is a great framework, but doesn't have anything built in that deals with this, so I did some research myself...

I ran into some great articles on the subject, which really helped me a lot (and I took some ideas from), but weren't perfect.

A great article on the subject is this one: http://www.bennadel.com/blog/2554-loading-angularjs-components-with-requirejs-after-application-bootstrap.htm

The important part is that it explains how to dynamically load angularjs directives (or other components) after bootstrapping your angularjs app.

What I didn't like about this article is that the writer's example requires RequireJS and jQuery along with all the AngularJS files you already have. This alone will make your app really heavy, and I think doesn't need to be like this.

Let me show you how I wrote a simple AngularJS service that can dynamically load directives.

The first crucial step is that you need to save a reference to $compileProvider. This is a provider that is available to us when bootstrapping, but not later, and this provider will compile our directive for us.

JavaScript
var app = angular.module('MyApp', ['ngRoute', 'ngCookies']);

app.config(['$routeProvider', '$compileProvider', function($routeProvider, $compileProvider) {
    $routeProvider.when('/', {
        templateUrl: 'views/Home.html',
        controller: 'HomeController'
    });

    app.compileProvider = $compileProvider;
}]);

Now, we can write a service that will load our JavaScript file on demand, and compile the directive for us, to be ready to use.

This is a simplified version of what it should look like:

JavaScript
app.service('LazyDirectiveLoader', 
['$rootScope', '$q', '$compile', function($rootScope, $q, $compile) {
    
    // This is a dictionary that holds which directives are stored in which files,
    // so we know which file we need to download for the user
    var _directivesFileMap = {
        'SexyDirective': 'scripts/directives/sexy-directive.js'
    };

    var _load = function(directiveName) {
        // make sure the directive exists in the dictionary
        if (_directivesFileMap.hasOwnProperty(directiveName)) {
            console.log('Error: doesnt recognize directive : ' + directiveName);
            return;
        }

        var deferred = $q.defer();
        var directiveFile = _directivesFileMap[directiveName];

        // download the javascript file
        var script = document.createElement('script');
        script.src = directiveFile;
        script.onload = function() {
            $rootScope.$apply(deferred.resolve);
        };
        document.getElementsByTagName('head')[0].appendChild(script);

        return deferred.promise;
    };

    return {
        load: _load
    };

}]);

Now we are ready to load a directive, compile it and add it to our app so it is ready for use.

To use this service, we will simply call it from a controller, or any other service/directive like this:

JavaScript
app.controller('CoolController', ['LazyDirectiveLoader', function(LazyDirectiveLoader) {
    
    // lets say we want to load our 'SexyDirective' all we need to do is this :
    LazyDirectiveLoader.load('SexyDirective').then(function() {
        // now the directive is ready...
        // we can redirect the user to a page that uses it!
        // or dynamically add the directive to the current page!
    });
}]);

One last thing to notice is that now your directives need to be defined using '$compileProvider', and not how we would do it regularly. This is why we exposed $compileProvider on our 'app' object, for later use. So our directive js file should look like this:

JavaScript
app.compileProvider.directive('SexyDirective', function() {
    return {
        restrict: 'E',
        template: '<div class=\"sexy\"></div>',
        link: function(scope, element, attrs) {
            // ...
        }
    };
});

I wrote earlier that this is a simplified version of what it should look like, since there are some changes that I would make before using it as is.

First, I would probably add some better error handling to look out for edge cases.

Second, we wouldn't want the same pages to attempt to download the same files several times, so I would probably add a cache mechanism for loaded directives.

Also, I wouldn't want the list of directive files (the variable _directivesFileMap) directly in my LazyDirectiveLoader service, so I would probably create a service that holds this list and inject it the service. The service that holds the list will be generated by my build system (in my case, I created a gulp task to do this). This way, I don't need to make sure this file map is always updated.

Finally, I think I will take out the part that loads the JavaScript file to a separate service so I will be able to easily mock it in tests I write. I don't like touching the DOM in my services, and if I have to, I'd rather separate it to a separate service I can easily mock.

I uploaded a slightly better (and a little less simplified) version of this over here: https://github.com/gillyb/angularjs-helpers/tree/master/directives/lazy-load

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)