One of the best things about the Angular router is its excellent support for lazy loading. We can use it to make the initial bundle of our application as small as possible by only shipping to the client what is needed to render the application’s first screen.

This series is about running our applications in the hybrid mode, where we use both AngularJS and Angular. One implication of this is that we have ship both of the frameworks to the client. We, however, don’t have to do it in the initial bundle.

In this article I will show how to set up a hybrid app such that AngularJS, NgUpgrade, and the existing AngularJS code are loaded lazily.

Read the Series

This is the fifth post in the Upgrading Angular Applications series.

Dual Router Setup

Suppose our application has the following four routes, where the Angular application handles “/angular_a” and “/angular_b”, and the existing AngularJS application (via the UI-router) handles “/angularjs_a” and “/angularjs_b”.

/angular_a /angular_b /angularjs_a /angularjs_b

Let’s also suppose that our application’s entry point is “/angular_a”.

Our goal is to treat this application as a regular Angular application and not worry about AngularJS, NgUpgrade or the UI-router until the user navigates to “/angularjs_a” or “/angularjs_b”. Only at that point we want to load the needed AngularJS code. Let’s see how we can do it.

Sibling Outlets

As you can see the root component implements the Sibling Outlets strategy (see here). It has a <router-outlet> Angular directive and an <div ui-view> AngularJS directive. In other words, it two sibling router outlets: one for Angular and the other one for AngularJS.

Note that <div ui-view> will remain a simple DOM element until AngularJS is loaded — only then the ui-view directive will be place there.

AngularJS Application

To illustrate this example, let’s sketch out a simple AngularJS application using the UI-router.

To make this AngularJS application ready for the hybrid app, let’s add a sink state to capture unmatched URLs, which, we assume, will be handled by the Angular application.

Angular Application

Next, let’s sketch out an Angular application.

And similar to the AngularJS application, let’s define a sink route to capture unmatched URLs.

The Angular application assumes that unmatched URLs are handled by AngularJS. For this to work we need to load AngularJS first, and that’s what the {path: ‘’, loadChildren: ‘./angularjs.module#AngularJSModule’} route does.

Loading AngularJS

AngularJSModule is a thin Angular wrapper around our existing AngularJS application.

To make the Angular router happy, we render an empty component. More importantly, we bootstrap the AngularJS application and set up the location synchronization.

Overview

Now when we have looked at all the pieces separately, let’s see how they work together.

When the user loads the page, the Angular application will get bootstrapped: The Angular router will redirect to ’/angular_a’, will instantiate AngularAComponent and will place it into <router-outlet> Defined in AppComponet.

Note we haven’t loaded AngularJS, NgUpgrade, or the existing AngularJS application yet, and navigating from ’/angular_a’ to ’/angular_b’ and back does not change that.

When the user navigates to ’/angularjs_a’, the Angular router will match the {path: ‘’, loadChildren: ‘./angularjs.module#AngularJSModule’} route, which will load AngularJSModule.

The bundle chunk containing the module will also contain AngularJS, NgUpgrade, and the existing AngularJS application.

Once loaded, the module will call upgrade.bootstrap. This will trigger the UI-router, which will match the ‘angularjs_a’ state and will place it into <div ui-view>. At the same time the Angular router will place an EmptyComponent into <router-outlet>.

When the user navigates from ’/angularjs_a’ to ’/angularjs_b’ and back, the Angular router will keep matching the sink route, and the UI-router will update the <div ui-view>.

When the user navigates from ’/angularjs_a’ to ’/angular_a’, the UI-router will match its sink route and will place an empty template into <div ui-view>. The setUpLocationSync helper will notify the Angular router about the URL change. The Angular router will match the URL and will place AngularAComponent into <router-outlet>.

Note, the AngularJS application keeps running — it does not unloaded (it is almost impossible to unload a real-world AngularJS application, so we have to keep it in memory).

When the user navigates from ’/angular_a’ to ’/angular_b’ and back, the UI-router will keep matching its sink route, and the Angular router will update <router-outlet>.

Finally, when the user goes back to ’/angularjs_a’, the Angular router will match its sink route. And the UI-router, which is already running, will match the appropriate state. This time we haven’t had to load or bootstrap the AngularJS application because it is only done once.

Preloading

As it stand right now, we will load AngularJS when and only when the user navigates to a route handled by AngularJS. This makes the initial load fast, but makes the user’s first navigation to ’/angularjs_a’ slow. We can fix it by enabling preloading.

With this in place, the router will preload AngularJSModule (and, as a result, AngularJS, NgUpgrade, and the existing AngularJS app) in the background while the user is interacting with our application. You can learn more about this in the Angular Router: Preloading Modules article.

More interestingly, the router will instantiate AngularJSModule, which in turn will bootstrap the AngularJS application. This means that the application will be instantiated in the background as well.

And that’s how we get the best of both worlds: our initial bundle is small and subsequent navigations are instant.

Code Samples

Upgrading Angular Applications Book

This article is based on the Upgrading Angular Applications book, which you can find here https://leanpub.com/ngupgrade. If you enjoy the article, check out the book!