CSS Lazy Loading in AngularJS

The simple way

A classical Angular app starts with a HTML file. In the head tag you’ll put all the link tags for your templates style, and under the body tag you’ll stick the ng-app directive.

The Problem

Every module should be an atomic unit:
Each module (controller,template and a CSS) should function as an independent unit. We should be able to take a module out and add new one without major changes to the whole app.

In a classical angular app, with each module template taking in out out you should maintain the link tags in the Head. With many templates this is no a pretty sight…

A template should be able to ask 3rd party style:
In addition to it’s own style file, a template may use a 3rd party UI lib (like AngularUI, AngularStrap or a custom directive with a CSS file).

Maintaining this dependencies in the head tag by hand is not a big fun and error prone.
In addition, multiple modules can use the same 3rd party lib, and we should make sure to load it’s CSS file only once because browsers are not smart to prevent it.

Sometimes we have no access to the head tag:
In our case, this application is wrapped with many JSP templates and pages, and it should be self contained. Touching the wrapping JSPs is not an option.

The first solution

I created an Angular service that can load a CSS file by appending a link tag to the head tag and I’ve using it in the controllers:

But this is not a good solution.
First, it don’t prevent loading the 3rd party CSS files over multiple times.
Second’ it is ugly! the CSS let the browser know how it should render the template. It’s place is in the template and not in the controller!

A better solution

I’ve created a new directive and used it in the templates.
I added a peace of logic to verify we are loading each style file only once:

app.directive('lazyStyle',function(){varloadedStyles={};return{restrict:'E',link:function(scope,element,attrs){varstylePath=attrs.href;if(stylePathinloadedStyles){return;}if(document.createStyleSheet){document.createStyleSheet(stylePath);//IE}else{varlink=document.createElement("link");link.type="text/css";link.rel="stylesheet";link.href=stylePath;document.getElementsByTagName("head")[0].appendChild(link);}loadedStyles[stylePath]=true;}};});// In the template:<lazy-stylehref="style.css"/>

Calculated paths

In my app, we are using a server mechanism to calculate the path to all of our resources, so the href value should be calculated:

<lazy-stylehref="style/myModule/style.css"/>

The problem is that the directive code is executed before the basicResourcePath is being evaluated which leads to a wrong CSS path.
The solution I’ve found is to use the ``$observe` on the attributes to the directive: