Work / 01/07/2015

Introducing AngularCSS: CSS On-Demand for AngularJS

A lot has changed on how we develop web applications these days. The languages have evolved, the tools have improved, and now we have technologies like AngularJS. All these things create interesting presentation layer challenges in modern web application development and most of these challenges are related to how we reference stylesheets on single-page apps. One way to overcome these challenges is to use the AngularCSS library. It optimizes the presentation layer of your apps by dynamically injecting stylesheets as needed.

So how do you implement AngularCSS into your apps?

The Early Days of the Web

These days we have responsive web design and media queries in CSS. This makes for larger files or more files dedicated to different breakpoints. We also have the concept of single-page apps. They usually feature a single master template file with a head tag, and multiple headless views or partials,leading us to reference all CSS files from the master template. This is not ideal.


The whole presentation layer of the app is being front-loaded, with potentially thousands of lines depending on the size of the app. This seems rather unnecessary due to the fact that the user may never access some parts of the app or breakpoints for that matter.


So how do we overcome this? By using AngularCSS to meet these challenges head on.


Introducing AngularCSS

Here at DOOR3, we have developed some very robust cross-platform single-page apps. Some of them covering from hand-held devices to kiosks or even TVs. All with a single codebase. At times with so much CSS, the load time and performance become a huge concern on the presentation layer. This is the reason we have created AngularCSS.


AngularCSS is a JavaScript library for Angular that optimizes the presentation layer of your single-page apps by dynamically injecting stylesheets as needed. This approach is also referred as “on-demand” or “lazy loading”. The main benefit of lazy loading is performance of course, but there are other great things we can leverage from this approach like Encapsulation and SMQ (Smart Media Queries).

Getting Started

Before we get into encapsulation and SMQ, lets get started with the library. Using AngularCSS is very simple. There are two basics steps for setting it up.



  1. Reference the JavaScript library in your index.html after angular.js.
    You can download AngularCSS from GitHub. Also available via Bower and CDN.
  2. Add “door3.css” as a dependency for your app.
    var myApp = angular.module('myApp', ['ngRoute','door3.css']);




Now you can start referencing stylesheets from the route provider.

$routeProvider .when('/tickets', { templateUrl: 'tickets/tickets.html', controller:'ticketsCtrl', css: 'tickets/tickets.css' });

Or directly in directives.

myApp.directive('itinerary', function () { return { restrict: 'E', templateUrl:'itinerary/itinerary.html', css: 'itinerary/itinerary.css' } });

Web Component Encapsulation

DOOR3 culture believes in progressive enhancement. We also believe that AngularJS and Web Components are the future of the web. In Angular we can attach templates (structure) and controllers (behavior) to pages and components. AngularCSS enables the last missing piece: attaching stylesheets (presentation). For us, being able to have these three things in a single unit is vital based on the way the web is moving towards component-based development.

Smart Media Queries

AngularCSS supports Smart Media Queries via the matchMedia API. This means that stylesheets with media queries will be only added if the breakpoint matches. Consider the example below. If a user accesses your responsive app from a mobile device, the browser will only load the mobile stylesheets because AngularCSS will first evaluate the media query and check if it matches before adding the stylesheet to the page. This can significantly optimize the load time of your apps.

$routeProvider .when('/tickets', { templateUrl: 'tickets/tickets.html', controller:'ticketsCtrl', css: [ { href: 'tickets/', media: '(max-width: 480px)' }, { href: 'tickets/tickets.tablet.css', media: '(min-width: 768px) and (max-width: 1024px)' }, { href: 'tickets/tickets.desktop.css', media: '(min-width: 1224px)' } ] });


Application Architecture

As you can already tell, this way of referencing stylesheets requires separate files for each breakpoint, page and component, opposed to having page-specific and component-specific CSS in the same files. This approach encourages an organized and scalable CSS architecture by separating stylesheets by sections, pages, components and devices.

AngularCSS reintroduces the concept of CSS scope on single-page apps. Stylesheets only live for as long as the current route or directives are active. This means that the chances of unwanted CSS overrides are slim to none. And because there could be use cases for persisting stylesheets, we’ve added a feature to persist stylesheets if desired.

$routeProvider .when('/page1', { templateUrl: 'page1/page1.html', controller:'page1Ctrl', css: { href: 'page1/page1.css', persist: true } });


CSS and Cache

It is possible to preload stylesheets before the route or directive are active. This is accomplished internally via HTTP request. That way when the stylesheets are added, they are loaded from the browser’s cache making it way faster to resolve.

$routeProvider .when('/page1', { templateUrl: 'page1/page1.html', controller:'page1Ctrl', css: { href: 'page1/page1.css', preload: true } });

Browser cache is awesome because it speeds thing up. But, busting the cache can be very helpful when publishing CSS updates to your app, since it forces the browser download the latest version of the stylesheet. This can be done similarly by setting bustCache to true.

$routeProvider .when('/page1', { templateUrl: 'page1/page1.html', controller:'page1Ctrl', css: { href: 'page1/page1.css', bustCache: true } });

All these examples illustrate how to configure features at the stylesheet level, but it is possible to do it globally by setting some defaults in $cssProvider via module config.

myApp.config(function($cssProvider) { angular.extend($cssProvider.defaults, {persist: true, preload: true, bustCache: true }); });


How AngularCSS works

In order to provide a seamless API integration with AngularJS, we had to figure out ways to intercept routes and directives. For routes, the AngularCSS service listens for route and states event changes. These events expose the current and previous route object. Since we are extending the route object with a custom “css” property, AngularCSS adds the CSS defined on the current route and removes it from the previous route via its service: $css. Same concept applies when using states with UI Router.

For directives, well, it is a little bit more complicated. Since Angular doesn’t expose events for directives we were forced to add them ourselves by hacking or “monkey-patching” Angular core. First, we had to get inside angular.module and angular.directive in order to get a list of all custom directives. Then proceed to decorate all directives and get inside the compile and link functions. Finally, we added a custom event via $rootScope.$broadcast that triggers as each directive is being compiled. We pass the DDO (Directive Definition Object) and scope containing our custom “css” property to the custom event. The scope is passed in order to remove the directive’s stylesheets on scope destroy event. After all this nonsense, AngularCSS is able to extend the AngularJS API in a native-like fashion. It is my hope that the AngularJS team and community embrace AngularCSS and with their help/collaboration a solution will arise.

“Because Angular is totally modular, you can easily replace any of its parts.”  - Brian Ford, Angular Team Member

With AngularCSS we can now encapsulate presentation in Angular pages and components in an organized and scalable fashion. There’s no need to reference the stylesheets via HTML with the tag anymore (yay!). Stylesheets are requested on-demand. Responsive design can be optimized at the breakpoint/device level. And we also get some useful features like busting cache.



Are you a developer? We'd like to talk to you.

Check our Careers page for openings.

How can we help?