Lazy load modals with Angular! 😴

Parham
Parham
Oct 17, 2020 · 8 min read

By default, NgModules are eagerly loaded, which means that as soon as the app loads, so do all the NgModules, whether or not they are immediately necessary. For large apps with lots of routes, consider lazy loading — a design pattern that loads NgModules as needed. Lazy loading helps keep initial bundle sizes smaller, which in turn helps decrease load times.
https://angular.io/guide/lazy-loading-ngmodules

To lazy load a feature module you can simply define a route for it and that’s it!
Here is an example to remind you how it’s done.

const routes: Routes = [
{
path: 'map',
loadChildren: () => import('./map/map.module').then(m => m.MapModule)
}
];

Now Angular CLI will separately bundle map module and it will only be loaded when you access the /map route.

What about modules used in the map that do not have a separate route?
Like modals or any piece of UI component that is not visible in view initially and will appear based on different workflows.

By default, all these modules will be loaded with the map module bundle, even though if the user never uses these features.

Lazy load feature modules in an already lazy-loaded feature module

In my application, I have a map view which is a feature module with the route /mapthat hosts a bunch of other features.

  • Search modals that will only be visible when the user is searching on the map.
Lazy loaded search result modal in invest.agriculture.vic.gov.au
Lazy loaded layer selector in invest.agriculture.vic.gov.au
Lazy loaded draw panel in invest.agriculture.vic.gov.au

I want to load these pieces of UI only if the user needs them.

Obviously, I can lazy load the other feature modules like map module but the caveat is that accessing routes that define other feature module means leaving and unloading the map which is not going to work for my use-case.
For example, I need to stay on the map and show the modal.
So I need to lazy load feature modules in an already lazy-loaded mapmodule.

This will help me to reduce the initial bundle size of the /map module to make loading of the map faster.

So the question is:
How to lazy load those feature modules without leaving the /map route?

Angular Auxiliary Routes to the rescue

Angular Auxiliary Routes let you lazy load parts of the view that are not visible and bundle those separately.

The following video shows how invest.agriculture.vic.gov.au uses this capability. Here I am starting in the context of /map route and all the JS required for the visible part of the view is loaded with the map module.
Pay attention to network requests when I open the search results modal. It’s only then that Angular loads the JS files required for the SearchModule and my modal.

Lazy loaded search result modal in invest.agriculture.vic.gov.au

invest.agriculture.vic.gov.au is too complicated for the learning purpose so I made an Example application that replicates the lazy-loaded modal and map.
This application is available on Github. So you can download and follow the same steps mentioned in the rest of my article.

Here is a demo of the final result.

Let’s see what is happening with the URL here

This is the syntax for the URL
http://base-path/primary-route-path(outlet-name:route-path)

http://localhost:4200/#/map/(map-outlet:modal)
  • http://localhost:4200 is the base path. I am using HashLocationStrategy which is why I have /#/ after base URL.

Let’s breakdown the auxiliary route

  • ( open and ) close parenthesis indicates the beginning and end of my auxiliary route.

How to build the auxiliary routes?

First, I need a secondary router outlet (named router outlet).
In Angular, you can have one default(primary) router outlets and as many named(secondary) router outlets as you need.

The RouterOutlet is a directive from the router library that is used like a component. It acts as a placeholder that marks the spot in the template where the router should display the components for that outlet.

<router-outlet></router-outlet>
<router-outlet name='map-outlet'></router-outlet>

Given the configuration above, when the browser URL for this application becomes /map, the router matches that URL to the route path /map and displays the MapComponent as a sibling element to the RouterOutlet that you've placed in the host component's template.

The primary router outlet will render /home.
The map-outlet (named router outlet) will render the map component.

In my case, both router outlets are in the app.component.html.

All I need now is to specify the outlet name in the map module router when I define the base route and place a 3rd <router-outlet> where I want to load map modules’ child routes. The 3rd <router-outlet> is placed in map.component.html .

Here is the top-level application router config defining the /map route which lazy loads the map module.

application router config, defining the /map route

Next, I need to define the child routes for the map module and use my named router outlet (map-outlet).
This is the code for the map module router that has a child route which use the map-outlet (named router outlet) to load ModalWrapperModule.

That’s all you need to define a route within a route and Angular will take care of bundling the lazy-loaded feature modules separately and also lazy loading the feature modules as you navigate to their respective route.
Here is the code structure for the map module.

Map feature module structure

Now you can easily use the Angular router to navigate between to Modal route. Take a look at the openModal() method!

map.component.ts

As you can see I am passing multiple commands to navigate method.

Basically telling the router to navigate to /map/(map-outlet:modal) where /map is the map module path, map-outlet is my named outlet and modal is the path for the modal module.

And the last piece of the puzzle to make the router work. Place a <router-outlet></router-outlet> in the map view HTML.

map.component.html

By putting an outlet in the map.component.html I am telling the Angular where to render the content of the named router outlet (map-outlet).

How does the code look like for the modal module?

The architecture for the module is a bit more complicated than the other feature modules in my application since with Angular material dialog I need to pass a component to thethis.dialog.open() method.
Here is how the code structure looks like.

Modal feature module structure

modal-wrapper.component.ts is a wrapper component that will open the modal in the OnInit when the module is loaded by modal route (/map/(map-outlet:modal)).

Plus it will clean up the route to remove the auxiliary route when modal is closed. I am using afterClosed method of Angular Material Dialog to detect when the user closes the modal and change the route to the /map and remove the auxiliary route. OnDestroy will take care of the scenario that user clicks browser navigation back button.
This way I can navigate between modal and map as many time as I want to show/hide the modal.

Here is the code for each file in the modal module.

modal-wrapper.module.ts
modal-wrapper-routing.module.ts
modal-wrapper.component.ts
modal.component.ts
modal.component.html

I have simplified the code here for this demonstration but you can imagine that a feature module can be way more complicated and use more services or other third-party dependencies.

So being able to lazy-load modules will be a great boost to the application performance.

Final notes:

  • As a result of this architecture, you modal is route enabled which means you can refresh the page and Angular will open the modal automatically.
    This is great for bookmarking and sharing of URLs.
    In this case, I can share the link with someone and they can see the search result for the exact lat/long I used for my search.

Also if your requirement is just to lazy-load a specific component like a modal and not the whole feature module, you might find the following article useful.

Thanks for reading my article.
I hope you find it useful.

Code道

The Tao (Chinese: 道; pinyin: Dào; literally: “the Way” )…

Code道

The Tao (Chinese: 道; pinyin: Dào; literally: “the Way” ) Code道— is a publication about all things app development. Our way — Learn through sharing

Parham

Written by

Parham

I am a mobile & web tech lead @LapisITAus , blogger, @Auth0Ambassador , 🐈 lover and passionate about UI/UX.

Code道

The Tao (Chinese: 道; pinyin: Dào; literally: “the Way” ) Code道— is a publication about all things app development. Our way — Learn through sharing

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