Boost Angular’s Performance by Lazy Loading your Modules
In this article, we will talk about lazy loading in Angular.
At the end of this article, we will gain a valuable insight into how to boost the performance of our Angular apps by loading our ngModules on demand.
We will need several tools in this article, Node.js and NPM. As this is an Angular project, it depends heavily on Node.js and NPM. As NPM bundles with Node.js, installing Node.js adds NPM to our machine.
We will not go through the process of installing Node.js, you can refer to the installation instructions. Instead, let’s dive into boosting your performance.
Tip: When using a component-based framework like Angular or React, use tools like Bit to easily share, reuse and sync your components across projects- to build faster with your team. It’s free, give it a try.
What is Lazy Loading?
lazy loading
is the process of loading modules(images, videos, documents, JS, CSS, etc) on-demand.
The most important concepts of application performance are: Response Time, and Resources Consumption. It is inevitable that they are going to happen. Also, a problem can arise from anywhere though, but it is highly important to find and address them before they happen.
The prospect of Lazy Loading in Angular helps reduce the risk of some of the web app performance problems to a minimal. Lazy Loading does well to check the concepts we listed above:
- Response Time: This is the amount of time it takes the web application to load and the UI interface to be responsive to users. Lazy loading optimizes response time by code splitting and loading the desired bundle.
- Resources Consumption: Humans are impatient creatures if a website takes more than 3 seconds to load, 70% of us will give up. Web apps should not take this long to load. So, to reduce the amount of resources loading, lazy loading loads the code bundle necessary at a time.
Lazy loading speeds up our application load time by splitting it into multiple bundles and loading them on demand.
Advantages of lazy loading:
- High performance in bootstrap time on initial load.
- Modules are grouped according to their functionality.
- Smaller code bundles to download on initial load.
- Activate/download a code module by navigating to a route.
How Lazy Loading works?
Lazy Loading generally, is a concept where we delay loading of an object until it is needed.
In Angular, all the JavaScript components declared in the declarations array app.module.ts
are bundled and loaded in one fell swoop when a user visits our site.
Let’s say we have an app that has three components: Home
, About
, and ViewDetails
. Also, it has three routes:
const routes: Routes = [
{
path: '',
component: HomeComponent
},
{
path: 'about',
component: AboutComponent
},
{
path:'viewdetails',
component: ViewDetailsComponent
}
]
@NgModule({
declarations: [
HomeComponent,
AboutComponent,
ViewDetailsComponent
]
})
We see from the above code, different router states our app can be in. A different route to a different state.
The issue here is that Angular bundles up all the components in the declarations
array and loads all of the components. It doesn't make any sense to load all the components when the user is only navigating to /
which should only load the corresponding component, HomeComponent
.
Lazy loading was implemented in the Angular router (@angular/router
) to fix this. The entire code bundle is code split
, and the router, lazily
load the code chunks on demand.
Let’s start Lazy Loading
To demonstrate lazy loading, we are going to create an Angular app. The app will have three components:
- Home: This will be our index page, it will be activated by the route “/”.
- ViewDetails: This component will display the info about a product. It will be assigned to the route, “/viewdetails”.
- About: This is our about page, it will display our info as a company. It will be assigned to the route: “/route”.
We are going to lazy load components About
, and ViewDetails
. They are going to have their own feature modules, that will be activated using @angular/router
's loadChildren
property. The Home
component will be the index page.
Project Setup
Here, we set up our project. We will be using the Angular CLI (ng
) utility throughout in this article. If you don't have it installed. Run the following command to install it:
npm install @angular/cli -g
With the -g
flag, we instructed NPM
to make it global, so that we can use the tool from any directory in our system.
Run the following to create a barebones Angular app:
ng new ng-lazy-load --minimal
Notice the --minimal
flag in our command. It tells the ng
command-line tool to create our Angular app without all the *.spec.ts
, *.css
, *.html
files. Everything will be inline i.e Components will contain everything (CSS, HTML) in one file.
Let’s take a look at our app.component.ts
file:
// src/app/component.ts
import { Component } from '@angular/core';@Component({
selector: 'app-root',
template: `
<p>
app works!
</p>
`,
styles: []
})
export class AppComponent {
title = 'app';
}
You see, we have inline styles and templates. All our HTML goes into the template
property, also, our CSS into the styles
property.
Scaffolding Angular apps with the minimal
flag also configures the .angular-cli.json
file, to create future components without the accompanying *.spec.ts
and *.css
and *.html
files.
Our project directory(ng-lazy-load
) will look like this:
+- ng-lazy-load
+- src
+- app
+- app.component.ts
+- app.module.ts
+- assets
+- .gitkeep
+- environment
+- environment.prod.ts
+- environment.ts
+- index.html
+- main.ts
+- polyfills.ts
+- style.css
+- tsconfig.app.json
+- typings.d.ts
+- .angular-cli.json
+- .gitignore
+- package.json
+- tsconfig.json
You see like we said earlier only component files are created. No tests(*.spec.ts
) are created. No CSS or HTML files.
Creating Angular apps this way is perfect for simple examples and demo purposes.
Creating the components
Now, we will create our components. To create the Home component, run the following command:
ng g c home create src/app/home/home.component.ts (250 bytes)
update src/app/app.module.ts (390 bytes)
The ng
tool has a plethora of commands to use to make development easy and stress-free. Here we used the shortcut notation of ng generate component
.
ng g c
is the same as typing ng generate component
. ng generate component
is used to add a component to an existing app. The generate
command have different sub-commands that can be used to add different features to an Angular app.
Here, the ng g c home
command:
- Creates
src/app/home
directory - Inside that directory, it generates
home.component.ts
file and updatesapp.module.ts
to import theHome
component and add it to itsdeclarations
array.
It generates only one file home.component.ts
without the extra *.spec.ts
. *.css
, and *.html
files because of the configuration in the .angular-cli.json
, we mentioned earlier.
Next, we create the About
module and component. If you noticed the Home
component has no module because it will not be lazily loaded. Only components activated by lazy loading should have a module.
So, run the following commands to create an About module and component:
ng g m about --routing
create src/app/about/about-routing.module.ts (248 bytes)
create src/app/about/about.module.ts (275 bytes)ng g c about
create src/app/about/about.component.ts (253 bytes)
Notice another new command ng g m
. This command is the shortcut notation for ng generate module
. It adds a module to our app. --routing
flag was added in our command to tell the ng
tool to create the module with a routing system.
So, our command ng g m about --routing
:
- creates a directory
src/app/about
- inside that directory,
about-routing.module.ts
andapp.module.ts
files are created.
about-routing.module.ts
file holds the routing information of the module and it is imported in about.module.ts
in its imports
array.
Next, we finish off by creating the last component and module, ViewDetails
:
ng g m viewdetails --routing
create src/app/viewdetails/viewdetails.module.ts (299 bytes)
create src/app/viewdetails/viewdetails-routing.module.ts (254 bytes)ng g c viewdetails
create src/app/viewdetails/viewdetails.component.ts (271 bytes)
update src/app/viewdetails/viewdetails.module.ts (383 bytes)
Creating a lazy-loaded module
We have actually created our lazy loaded modules above, here we will implement routing for each of the modules.
About module
As we noticed above during its creation, two modules were created, about.routing.module.ts
and about.module.ts
.about.routing.module.ts
holds the routes of the module and it is imported in the imports array of about.module.ts
.
We will configure our routes in about-routing.module.ts
, by inserting objects inside the routes
array:
// src/app/about/about-routing.module.ts
...
const routes: Routes = []
...
The routes: Routes
array is used to define all the possible router states an app could be in. It takes an object with two basic properties: path
and component
.
Insert the following inside it:
// src/app/about/about-routing.module.ts
...
import { AboutComponent } from "./about.component";const routes: Routes = [
{
path: "",
component: AboutComponent
}
]
...
Here, we used an empty path
to denote default/index route. We imported AboutComponent
, so that we can assign it to the component
property in the routes
array. The @angular/router
uses the component
property to know which component to load on the browser DOM.
ViewDetails module
We will do the same thing as we did above. import the ViewDetails
component and configure the routes:
// src/app/viewdetails/viewdetails-routing.module.ts
...
import { ViewdetailsComponent } from './viewdetails.component';const routes: Routes = [
{
path: "",
component: ViewdetailsComponent
}
]
...
Lazy loading the new module
We are done with our feature modules. Now, we set up the parent module, app.module.ts
.
We import the Home
component as it will be used in route configuration:
// src/app/app.module.ts
...
import { HomeComponent } from './home/home.component';
...
Next, we import the Routes
and RouterModule
:
// src/app/app.module.ts
...
import { HomeComponent } from './home/home.component';
import { Routes, RouterModule } from '@angular/router';
...
OK, we now create routes
variable that will hold our routes array, just like we saw in our feature modules:
// src/app/app.module.ts
...
import { HomeComponent } from './home/home.component';
import { Routes, RouterModule } from '@angular/router';const routes: Routes = [
{
path: "",
component: HomeComponent
},
{
path: "viewdetails",
loadChildren: "app/viewdetails/viewdetails.module#ViewdetailsModule"
},
{
path: "about",
loadChildren: "app/about/about.module#AboutModule"
}
]
...
So, here we made Home component the default route by using the empty path:""
. Looking at the next two routes, you'll notice something strange there, there is an odd-looking syntax with loadChildren
, then followed by #
with a module name. These routes are the routes we want to be lazy-loaded and were set up in feature modules: AboutModule
and ViewdetailsModule
.
The loadChildren
property is how we tell Angular to lazy load a module. The first part before the #
points to the relative path of the feature module. The second part will be the class name of the feature module. Also, if you noticed, we didn't import the feature modules, it is left for the parent module, AppModule
to dictate their URLs and somehow import
the modules.
This is how feature modules are lazy loaded in Angular:
loadChildren: "PATH_TO_FEATURE_MODULE_#_FEATURMODULE_CLASS_NAME"
There are a few important things to note here:
- The
loadChildren
property is used instead ofcomponent
. - We defined not only the path but, also the name of the feature module class.
Testing our App
Now, we have our app all set up and all our features modules correctly implemented. We need to test the app, to see them in action. We will do one last thing before we test.
- If you’re using Bit, you can also test individual components in the cloud.
We have to create a router-outlet in app.component.ts
to tell Angular where to render our components. Open up app.component.ts
and add the following to the template
tag:
// src/app/app.component.ts
...
template: `
<!--The content below is only a placeholder and can be replaced.-->
<a routerLink="/viewdetails">Dashboard</a> |
<a routerLink="/about">About</a>
<router-outlet></router-outlet>
`
...
We added the <router-outlet></router-outlet>
tag and also added some links to navigate to About
and ViewDetails
components.
Now, we serve our app:
ng serve
If you noticed in your terminal, there are extra code bundles generated apart from the normal bundles:
- inline.bundle.js
- main.bundle.js
- styles.bundle.js
- polyfills.bundle.js
- vendor.bundle.js
webpack: Compiling...
Date: 2018-04-21T15:42:51.440Z
Hash: 2a089415b0d05889be86
Time: 1039ms
chunk {about.module} about.module.chunk.js () 10.2 kB
chunk {inline} inline.bundle.js (inline) 5.79 kB [entry]
chunk {main} main.bundle.js (main) 24.2 kB [initial] [rendered]
chunk {polyfills} polyfills.bundle.js (polyfills) 555 kB [initial]
chunk {styles} styles.bundle.js (styles) 34.1 kB [initial]
chunk {vendor} vendor.bundle.js (vendor) 8.07 MB [initial]
chunk {viewdetails.module} viewdetails.module.chunk.js () 10.8 kB
Angular generated extra bundles about.module.chunk.js
and viewdetails.module.chunk.js
. These are the feature modules we configured to be loaded on demand. These extra module chunks will be loaded when either of the routes /about
and /viewdetails
is navigated to.
Navigate to http://localhost:4200
on your browser of choice. Open JavaScript Console
> Network
.
NB: I would advise using Google Chrome browser.
Here, you will see JS
, HTML
, CSS
etc files that are loaded by the application. You can filter files you want to see, by clicking on the Filter
tab.
You see that inline.bundle.js
, main.bundle.js
, styles.bundle.js
, polyfills.bundle.js
,vendor.bundle.js
are all loaded initially. Viewdetails
and About
modules were not loaded, they will if we navigate to their routes.
OK, now we want to go to /viewdetails
link. Remember, the /viewdetails
route is configured to lazy load (load on demand) so it didn't load when we navigated to the default route(/
) above. Click on viewdetails
link on the app:
You see viewdetails.module.chunk.js
being loaded in the Network
tab.
To see the same thing happen on our About
module. Click on the About
link.
Note, once a lazy loaded feature module is loaded, it doesn’t get re-loaded when it’s route is navigated to again.
That’s it, lazy loading
has been successfully achieved in Angular.
Conclusion
We have seen how important it is, to lazy-load modules in Angular and also, how it effectively improves web app performance.
Lazy loading was easily achieved by using the loadChildren
property.
Let’s recap on the advantages of lazy loading in Angular:
- Lazy loading helps decrease load time (faster download speeds).
- Modules are loaded on demand.
- Modules are loaded when the user navigates to their routes.
- Lazy loading decreases resources consumption (lower resource costs).
- Lazy loading doesn’t load everything once, it loads only what the user expects to see first.
That’s it. Please, feel free to ask if you have any questions or comments in the comment section.
This GitHub repository contains the final code developed throughout this article!
Thanks !!!