Lazy-loading CSS in your AngularJS app

Gerard Sans
Jan 29, 2015 · 4 min read

Download and inject stylesheets on demand using $route.resolve

Image for post
Image for post

Follow me on Twitter for latest updates @gerardsans.

Meet the monolithic stylesheet

It’s common practice to use one big CSS file for the whole application. This file is usually the result of various build steps: compilation, concatenation and minification. Tools such as Gulp or Grunt are great at this.

Relying on one big stylesheet is all good and fine for small projects but what happens when the website keeps growing and more rules are added to it? As you keep adding new sections and components, it gets bigger and bigger. One day it hits your desk… The first visit to your app is taking few seconds to load… You dig a little into it and find that there is a big monolithic CSS sitting in your browser.

Image for post
Image for post

What options are there for you now? Well, you clearly have to split your CSS into separate files. A sensible option would be to make a stylesheet for the different sections of your application.

This is just one of the reasons why you would want to cut down on CSS. Here’s some other scenarios why you should lazy-load your stylesheets:

  • Mobile performance. A slim CSS bundle will load faster and render quicker (critical rendering path). This is especially true for mobile devices. After loading just the minimal styling, you can progressively load more CSS as needed.
  • CSS reflow. You are already loading CSS files using the link element but are facing CSS reflow issues you want to avoid. Remember the browser has to download the content after adding the link element and even after that, the new stylesheets take few more ms to be available to the DOM.

In Angular your routes are a good place to start when chopping up your application into sections. Each route already loads its corresponding template and attaches the controller. It would make sense to also load stylesheets this way, but unfortunately that’s not supported. You could inject CSS into your template but then you will be missing out on browser caching and making your templates bigger.

Splitting CSS files using $route.resolve

In order to inject our CSS file while navigating to a new route we will use $route.resolve. We are going to use resolve in a way that navigation will only happen after we have injected our CSS. See $routeProvider.when() for more details.

We moved the actual code to a factory Service called injectCSS that contains a function set(id, url) returning a promise. This is the actual implementation of the service.

In order to inject our CSS we:

  • inject a link tag into the head element
  • hook to link.onload event (fires only in some browsers)
  • check document.styleSheets for changes

In case the CSS fails to load, Eg. network failure, it will keep on going after the browser adds an entry on styleSheets.

Findings

While working on the final solution I found out about a couple of things I didn’t know before:

  • dynamically injecting a CSS files is not as easy as it seems. There are lots of quirks along the way. This solution only covers the surface and I am just getting to see why the Angular team left it out.
  • $http apparently doesn’t use browsers cache opposed to the href on the link element. $http will always make an initial request even after being cached on the browser using the link tag.
  • It makes no difference having called $http with the same CSS file. The link tag will ignore it and take the same time to parse it and paint it on screen.

Resources

Find an online workbench I used to try different configurations here. Have any questions? Ping me on Twitter @gerardsans.

Special mention

Thanks Gert Hengevelt for kindly reviewing this post and publishing it at Opinionated AngularJS.

Image for post
Image for post

Opinionated AngularJS

Practical tips and best practices for developing AngularJS…

Gerard Sans

Written by

Developer Advocate @AWSCloud | Just be AWSome | MC Speaker Trainer Community Leader | Views are my own | @fullstackcon @ReactiveConf @ngcruise @UphillConf UK ☂

Opinionated AngularJS

Practical tips and best practices for developing AngularJS applications

Gerard Sans

Written by

Developer Advocate @AWSCloud | Just be AWSome | MC Speaker Trainer Community Leader | Views are my own | @fullstackcon @ReactiveConf @ngcruise @UphillConf UK ☂

Opinionated AngularJS

Practical tips and best practices for developing AngularJS applications

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store