Welcome to the Ivy League: Lazy Loading Components in Angular v9

Netanel Basal
Netanel Basal
Published in
6 min readFeb 18, 2020

--

I have great news! Exciting days have come. Angular version 9 was released, and one of its most powerful features is the ability to render components lazily and, most importantly, easily. In this article, we’re going to see several techniques to lazy load components with Ivy and what are the cool things we can do with them are.

Let’s skip the boring introduction that no one reads and see how we can leverage lazy loading to make our initial bundle smaller, and the application load time faster.

Lazy Load Components

First, let’s create a brand new FooComponent:

We’ve created a simple component that, for now, contains only HTML. Now let’s see how can we lazy load this component on demand when a user clicks a button:

A few things are going on here, let’s explain them one by one. When the user clicks the button, we load the FooComponent by using Webpack import function. When Webpack comes across this function, it automatically starts code-splitting your application.

In our example, it’ll create a new chunk for the FooComponent file, and not include it in the main bundle. If you’re using the Angular CLI, everything is already configured for you, and you can start using it immediately.

Now that we’ve loaded the component type, that which in Ivy, holds all the information Angular needs to create a component ( I’ll expand on this topic in a future article), we can use the ngComponentOutlet directive. The ngComponentOutlet directive receives a component type, instantiates it, and inserts its host view into the current view.

Note that there is no need anymore to add the component to the module’s entryComponents array. Angular finds the component on its own ✌️

The FooComponent can be a smart component that doesn’t receive any inputs. We can use DI to inject any service that it needs and let the component manages itself. For example:

That’s nice, but let’s face it, our component is useless. How do we use other Angular features in it, such as components, directives, or pipes? The answer is simple. We create a FooModule as we’d typically do, and require all the things we need:

The neat thing is that thanks to tree-shaking, if the only place that we use ReactiveFormsModule is inside the FooComponent, we’ll only load its source code whenever we load the FooComponent.

At this point, you’re probably wondering, how does it work? The only thing we did is to declare a module. We didn’t even import it.

The way it works is that the Angular compiler analyzes your NgModules and figures out what components or directives are available to the component in its module scope. Once it has this data, it adds it to the component definition.

If we take a look at the compiled code, we can see that it adds the FormControl directives under the component’s definition directives key:

Working with Inputs and Outputs

When working with components, there are times where we need a way to pass inputs and subscribe to outputs (i.e., events). Let’s see two ways to pass data to a lazy loaded component.

The first technique continues to use our FooComponent and the ngComponentOutlet directive. If we want to pass data to a component that uses ngComponentOutlet, we can use DI, create an Injector, and pass it to the ngComponentOutletInjector directive that works in conjunction with the ngComponentOutlet directive:

We create a new injector by using the create method that takes a list of providers and a parent injector to look in case it doesn’t find the requested provider in the current one. Now we can use this data inside the FooComponent:

This approach is suitable for cases that our component doesn’t expose events that we want to listen to. Let’s see the second way by creating a new BarComponent:

Now let’s lazy load BarComponent, and see how we can set the title input, and subscribe to titleChanges:

Here we can see the imperative way of ngComponentOutlet. First, as before, we need to load the component type. Next, we inject the ComponentFactoryResolver provider and obtain a reference to a ViewContainerRef using the ViewChild decorator.

We’re calling the createComponent() method with the factory. Internally, this method calls the create() method from the factory and inserts the hostView as a sibling to our container.

Now that we have a reference to our new component instance, we can set inputs and subscribe to events. Note that we’re not using the Input and Output decorators. This is because we’re not consuming the component from the template. We communicate with the component instance directly — more on that in the attached article at the end.

If that’s a lot of work for you, here’s a POC that will let you do the following:

Webpack Magic Comments

Let’s see two useful magic comments we can leverage when using the import function:

webpackChunkName:

When we create the production bundle for import(`./bar/bar.component`), Webpack names the chunk as [id].[hash].js. Sometimes this isn’t so useful, so we can instruct Webpack to give it a name we choose:

Now the chuck name will be bar.[hash].js.

webpackPrefetch:

We can use import(/* webpackPrefetch: true */`./bar/bar.component`), and Webpack will append a <link rel="prefetch" href="bar-chunk.js"> to the head of the page, which will instruct the browser to prefetch in idle time the bar-chunk.js file.

This can boost the loading time of a resource that we know ahead of time that the user is going to consume. Let’s see this in action:

We can see that the browser loads the resource, and when we explicitly request it again, it’s already cached.

For more Webpack magic comments, refer to the documentation.

We Can Do More!

Let’s finish with an advanced use case. Let’s say we have a dashboard where users can create widgets. We have 50 types of widgets where each widget contains a world of components, directives, pipes, and providers.

It would be a waste to add each widget component to our main bundle because then all clients would have to download and parse it, even though they never use it.

What if we could lazy load only the widgets that exist in the client’s dashboard? Let’s do it. For the demo, we’ll create two widgets, bar and funnel:

You may notice that we export each widget as default. The reason is that I don’t want to maintain an object where the key is the widget type, and the value is the corresponding type. (e.g. { bar: 'BarComponent' } ).

Next, let’s create a DashboardService that simulates a response from the server with the client’s saved widgets:

Now for the exciting part where we render the widgets — the DashboardComponent:

We get the response from the server and map each widget type to the corresponding widget file, and load it. Our import statement causes every .component file in the ./widgets directory to be bundled into a new chunk.

At run time, when the variable widget.type has been computed, any file will be available for consumption.

Additional Resources

🚀 In Case You Missed It

Here are a few of my open source projects:

  • Akita: State Management Tailored-Made for JS Applications
  • Spectator: A Powerful Tool to Simplify Your Angular Tests
  • Transloco: The Internationalization library Angular
  • Forms Manger: The Foundation for Proper Form Management in Angular
  • Cashew: A flexible and straightforward library that caches HTTP requests

You can find the complete code in this repo.

Follow me on Medium or Twitter to read more about Angular, Akita and JS!

--

--

A FrontEnd Tech Lead, blogger, and open source maintainer. The founder of ngneat, husband and father.