About lazy loading AngularJS modules

I recently wrote a post about creating pluggable AngularJS views using lazy loading of Angular modules. After discussing this topic with colleagues, I realized I did provide a technical solution but not much background as to why it is useful, what should be lazy loaded and when does lazy loading make sense. Hence this post…

Does lazy loading always make sense?

First, as general rule, optimization should only be performed when you actually have a performance problem. Definitely not in a prophylactic way. This is a common error made by many software engineers and causing complexity to unnecessarily increase at the beginning of a project. If you identify a bottleneck while loading specific resources, you should start thinking about introducing lazy loading in order to speed up your app e.g. during initial loading.

But just lazy loading everything without first having a good reason to introduce it can very well result in worse performance. You need to understand that lazy loading could actually increase the overall cost of loading resources. By loading all resources upfront, you get a chance to combine your resources and reduce the number of HTTP requests to your server. By splitting the loading of resources, you then actually have less possibilities to reduce the overall resource consumption by combination. Especially when you are loading many small resources, the overhead of loading them individually increases. Since JavaScript code is relatively small compared to static assets like images, lazy loading could cause a higher request overhead.

Also loading resources on demand means that when the user of your application activates a view which has not yet been loaded, he first has to wait until the additional resources are lazy loaded and properly registered. When you load everything upfront, the user has a longer wait time on application load time but can immediately access all views in your application without delay.

So using lazy loading always means having a tradeoff between initial load time of the application and subsequent activation of parts of your application.

RequireJS vs. AngularJS Dependency Injection

RequireJS is currently the state of the art in modular script loader and asynchronous and lazy loading of JavaScript files. It is based upon the AMD (Asynchronous Module Definition) API. It handles loading your script files in the browser based on a description of dependencies between your modules.

This may sound similar to AngularJS dependency injection mechanism. But RequireJS and AngularJS DI work on two completely different levels. AngularJS DI handles runtime artifacts (AngularJS components). In order to work properly, AngularJS DI requires all of the JavaScript code to have been loaded and registered by the framework. It injects controllers, directives, factories, services, etc. which have been previously loaded.

Since there is no direct integration of RequireJS and AngularJS and AngularJS has a single initialization phase where the definition of modules is interpreted, when using both of them, RequireJS will need to first load the complete dependency tree before AngularJS DI can be used. This effectively means that your complete JavaScript code will need to be fetched on initial load.

So we cannot just lazy load all AngularJS modules JavaScript files with RequireJS after an AngularJS application has started because the AngularJS DI container wouldn’t handle the components created as a result of loading the JavaScript files. Instead, you need to manually handle all these components which have been loaded after the startup phase of your application.

What’s the point of lazy loading ?

Lazy loading components generally improves an application’s load time. If your web application takes many seconds to load all components ever required to interact with user, you will most probably experience a high bounce rate with users just giving up before they even get a chance to see how great your application is.

By lazy loading parts of your application, you can make sure that the views in your application which are used immediately by most users are available immediately. If a given user decides to use other (e.g. more advanced views), the application starts loading required components on demand.

Especially if you are building a web application which can be used on phone and tablets (which represents about 60% of the total web traffic now), you have to consider that most users will not have a 4G mobile connection and initial load times can become prohibitive in an environment where the download bandwidth is limited.

Loading time is a major contributing factor to page abandonment. The average user has no patience for a page to take too long to load. Slower page response time results in an increase in page abandonment. Nearly half of web users expect a site to load in 2 seconds or less, and they tend to abandon a site that isn’t loaded within 3 seconds.

So improving the initial load time of your web application is critical. And this is the main use case for lazy loading.

When and what to lazy load ?

As explained above lazy loading makes sense when you want to reduce the initial load times and are ready to accept that loading additional views might not be instantaneous. Ideally, at some point in time during the run time of your application, you would be able to determine that the user will need a specific view and load it asynchronously in the background. Unfortunately, this kind of smart lazy loading is very difficult to implement for two reasons. First, it is not so easy to predict that a user will need a specific view. Second, asynchronicity introduces additional problems which increase the complexity of your application.

This is why lazy loading is mostly implemented in such a way that when a user activates a view (or a sub-part) of your application which hasn’t yet been loaded, it is loaded on the fly before displaying it.

A loading mechanism can be implemented on different levels. Having a fine granular lazy loading mechanism, reduces the maximum wait time for the user when something is loaded. But the complexity of your application grows (potentially exponentially) as well, the more fine granular it is. Since our general rule is not to optimize upfront but only find a solution to problems you are actually facing, this requires a strategy along these lines:

  1. First load everything upfront (this is how most desktop applications work). If you face problems because of high initial load time, continue optimize. Otherwise you are done.
  2. Lazy load individual modules of the application. This is the way I handle lazy loading in the application I used as a basis for my previous post. If the load times are acceptable, then stop optimizing. “Acceptable” could either mean that the load times for all parts of the application are good enough or that the load times are good for 95% of the use cases and the user only has a longer wait time for rare use cases.
  3. Keep reducing the granularity of lazy loaded resources…
  4. Until the performance is acceptable.

AngularJS components vs. Module loading

If you google for AngularJS lazy loading, you will find many resources. Most of them teach you how to lazy load controllers, directives, etc. The difference between these approaches and i.e. the one described in my previous post is basically that when you just lazy load controllers and such, you have a single module which is initialized at the beginning and for which you register additional components on the fly. This approach has two drawbacks:

  1. You cannot organize your application in multiple AngularJS modules.
  2. This doesn’t work well for third-party party AngularJS modules.

Both of these drawbacks have the same root cause. Since AngularJS does not only rely on JavaScript files to be loaded but also need to have the modules properly registered in order to inject them using DI, if this step is missing because the files were loaded later on, new AngularJS modules will not be available.

That’s why you need to additionally handle the invoke queue, config blocks and run blocks when lazy loading new AngularJS modules.

Also note that whenever the term “module” is used in this context, it can mean two different things:

  1. AMD module as used in RequireJS. These modules just encapsulate a piece of code which has load dependencies to other modules. These are designed for asynchronous loading.
  2. AngularJS modules. These modules are basically containers for controllers, services, filters, directives…

When I reference modules in this article, I mean the second kind of modules.

Conclusion

I hope that with this article I made it clearer, why lazy loading AngularJS modules is not a stupid idea but should be handled carefully. You need to make sure that you choose the right level on which to lazy load components. And if you need to split your application in multiple modules or use third-party modules, it is definitely not sufficient to use most of the mechanisms you will find by quickly googling for lazy loading in AngularJS. Using RequireJS is definitely a first step in the right direction but you need to make sure that the loaded scripts are also made available to the AngularJS dependency injection container.

 

2 thoughts on “About lazy loading AngularJS modules

  1. Very good article. The philosophy / reason for lazy loading of JS components that you have so well explained can be extended, albeit in a very restricted way, to client server apps that work on a huge database.

  2. While I agree with many of the arguments for and against the use of dynamic loading, the article sounds more “negative” than I think is really the current reality. some of us have been experimenting for some time now with these concepts, and they work reasonably well give that you can essentially create unlimited size single page web apps without having to load all assets at one shot.
    Case in point is that sites like Google do this for nearly everything, although it is very complex. So in my opinion, the way forward will be to minimize the complexity of dynamically loading assets, as opposed to deciding whether to dynamically load or not.
    At some point, many project seem to grow beyond the capabilities of loading all assets in one shot.

Leave a Reply

Your email address will not be published. Required fields are marked *