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?
Especially when you are loading many small resources, the overhead of loading them individually increases.
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
What’s the point of lazy loading ?
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.
- 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.
- 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.
- Keep reducing the granularity of lazy loaded resources…
- Until the performance is acceptable.
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:
- You cannot organize your application in multiple AngularJS modules.
- This doesn’t work well for third-party party AngularJS modules.
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:
- 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.
- 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.
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.