Introducing AngularCSS: CSS On-Demand for AngularJS

Posted By Alex Castillo In Technology 01/7/2015
angularjs css

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

Before we dive into AngularCSS, lets take a step back to the early days of the web. The presentation layer is not what it used to be. It has changed and while overall evolution has been positive, modern presentation layer architecture has some weaknesses. Historically, while creating a website, there would be an index.html file and other html files such as about.html, contact.html, etc. With that approach you could organize your CSS in the same fashion by referencing different stylesheets in the head section of each document accordingly: home.css, about.css, contact.css, etc. There are some great benefits that come with this, one of them being that stylesheets are loaded as they are needed and removed when they are not in a page-by-page basis.

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' } });

See full demo.

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/tickets.mobile.css', 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.

See the hack here.

“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.

*Alex Castillo is DOOR3’s Associate Director of Engineering. He is the brain behind the idea and development of AngularCSS. We want to hear from you! What do you think of AngularCSS?

Comments

It's great to see CSS being brought into the Angular fold. This is excellent not only for performance optimization, but for modularizing and organizing CSS within Angular apps as well. I can't wait to use this on my next Angular app!

Alex Castillo's picture

Thanks Rocky!

always prefer angular JS over other JavaScript frameworks. Planned to implement more concepts in my forthcoming projects

Alex Castillo's picture

Great! Let me know how it goes and if you have any questions.

That was something many programmers waiting which was present in meteor.
I will test with ionic and some other CSS packages.
Do you have any advice ?
Regards

Alex Castillo's picture

Hi Gokhan,

I suggest you put your global and third-party stylesheets (ionic, bootstrap, etc.) on your head tag via link reference.
Use AngularCSS for your page and component specific stylesheets.

Best,

Hey there Alex, first of all its a pretty damn cool plugin - just what I need for my project.. However once i set the css property in a directive, my link function is called with no arguments. So scope, and element are both undefined.
Is there a fix for this?

Alex Castillo's picture

Hi Anders,

I just pushed a new release (v1.0.6) including this fix.
Issue details can be found here: https://github.com/door3/angular-css/issues/2

Thanks for reporting the issue!

Thanks Alex, that was fast :)

thanks for sharing

Pages

Leave a Comment

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
To prevent automated spam submissions leave this field empty.
By submitting this form, you accept the Mollom privacy policy.