We have seen many examples of lazy loading components in Angular using route based loading. In this blog, we would be looking at a use case that involves data sources like a CMS, which provides data where we don’t know what components to load and in what order. To address this issue, we would be loading components dynamically based on the API response at the run time. Instead of loading all the components in the initial load, we only load components based on the data we receive from the API. This way, we lower the size of the initial bundle, fetched when the web site is loaded, which in turn reduces Time to Interaction. This leads to faster page load, and we also save users bandwidth, which in certain parts of the world means saving them money.
In this blog post, we will be following these steps to set up lazy loading of components:
- Setting up routing to use a master component.
- Adding reusable components and corresponding modules for these components.
- Creating a service that will help us in loading components lazily.
- Configuring the master component to use this service to render components.
1. Setting Up Routing To Use A Master Component
The application is architected in such a way that all the routes are directed to a common Master component. We will start by creating a master component which would act as a container for all the components that would be loaded and rendered dynamically. Right now, we will just create the component so that it can be referenced in the routes.
ng generate component components/master
We will further configure the Master component in Step 3. An error component can also be added similarly for redirecting to error pages. It can be as simple as this:
This would be the part of the initial bundle which is loaded when we open the web site. The routing is set up as below in the app-routing.module.ts:
We have added following routes which all load the same master component:
- :root - parent level route
- :root/:page - child-level route
- :root/:page/:subpage - nested child route
The data we are expecting from API contains all the data for components that are to be rendered on the current page. A different URL will have a different set of components and related data for that page. The data in JSON format looks like this:
The API consists of a components array. This array contains objects which have two main properties:
- type: a string specifying what component to render. This property is the reference property that would be used by angular to load the appropriate component.
- data: an object having data specific to the type of the component.
2. Adding Reusable Components And Corresponding Modules For These Components
We can add as many reusable components we want whose loading and rendering will be controlled based on the data we receive from the API. We also add a module that bundles the relevant components inside it. This is done as Angular only supports lazily loading modules and not the component itself. In the module, a static property: rootComponent is declared to hold the reference of the component to be rendered. This is done because it is the only way our service would be able to access and determine what kind of component to create. Here's a paragraph component has created as an example:
Components that have child components need not have a new module created for them as the data is passed on from the parent component. They are just needed to be declared in the declarations array of the parent module. For example, if a component gallery has a child component gallery-image, the declaration would like:
3. Creating A Service Which Will Help Us In Loading Components Lazily
We will create a service that will load the component code lazily as per the data received from the API.
The code is explained below:
- We define a lazymap which has the type of the component and the corresponding loadChildren function for loading the module. The component name is the key to the map and must match component type coming from the API.
- The loadAndRenderComponents method of the service takes in the data coming from the API and a container reference of the master component, which we clear before rendering components in it.
- Looping through the components array from API, we load the module factory to create the module reference moduleRef based on the type of component. We have used await here as the loadChildren function returns a promise which needs to be resolved before we move on to the next component.
- An intermediate condition (checking moduleOrFactory) whether the compilation is AOT or JIT, is evaluated to load moduleFactory.
- This moduleRef is used to get the component factory for creating the component.
- Once the component is created, the data object of the component received from the API is passed to the component’s data property for rendering it. We cast the component reference instance to an instance of Dynamic class, which has a parameter called data, to which we pass on the data. It is assumed that all the dynamic components will have at least one Input member called data.
Also, add the lazy-loaded component module path to the lazyModules array in angular.json
4. Configuring The Master Component To Use This Service To Render Components
The master component has the container in which all the dynamic components are rendered. The master component fetches the data from the API. This data is sent to the lazy service which fetches the required component code and renders the component.
With everything set up, we can check the bundles loading in the Dev tools under the networks tab (Use JS filter to view more clearly). While visiting a new page, if a new component is encountered, the corresponding module containing the component is loaded at runtime.
Feel free to download the source code from: https://github.com/sagarrchauhan/angular-lazy-loading-components
Or check out the code on Stackblitz: https://stackblitz.com/edit/github-dggene