facebook

Blog

Stay updated

Let’s see how to import modules efficiently
Using Lazy Loading pattern in Angular
Tuesday, May 04, 2021
angular lazy loading

One of the fundamental concepts of Angular are the Modules called NgModules. We can define a NgModule as a container of blocks of code that belong to the same flow, to the same functionality, or are dedicated to a specific application domain. Within these modules, we can find components, services, and other code files whose purpose is defined by the NgModule itself. 

Each Angular application has at least one module, the root module, which by convention is called AppModule and is defined in app.moduel.ts. Here is an example of how it is structured

import { NgModule } from '@angular/core'; 
import { BrowserModule } from '@angular/platform-browser'; 
@NgModule({ 
        imports:     [ BrowserModule ], 
        providers:    [ Logger ], 
        declarations: [ AppComponent ], 
        exports:      [], 
        bootstrap:    [ AppComponent ] 
}) 
export class AppModule { } 

Many other modules can be defined in complex applications besides the AppModule, adding them to the imports array. By default, the modules declared in the imports array are loaded when the application starts, even if they are not immediately used.

Of course, this has a cost in terms of time, especially if it is an application that owns many routes that belong to different modules.

It is good to apply the Lazy Loading design pattern in these cases, which allows us to load the modules only when we need them, obtaining a smaller initial bundle and a faster application loading.

The use of this pattern in our application requires only few steps. First of all, our application must have routing management through the import in the AppModule of the NgModule RouterModule and the use of the following instruction inside the import array: 

imports: [ 
                RouterModule.forRoot([  
                {path: 'first', component: FirstComponent},  
                {path: 'second', component: SecondComponent},  
                ]), 
... 
] 

In this way, we can see in our application a specific view based on the navigation path in the URL.

Also, remember to add the RouterModule to the list of exports to import it from the different modules used in the application. 

exports: [ 
                RouterModule 
] 

The second step is to replace the component that we want to show to a given path using the instruction:

loadChildren: () => import('./first/first.module').then(m => m.FirstModule) 

you get

imports: [ 
                RouterModule.forRoot([  
                        {path: 'first',  loadChildren: () => import('./first/first.module').then(m => m.FirstModule) },  
                        {path: 'second', component: SecondComponent},  
                ]), 
... 
] 

And adding routing in FirstModule too

const routes: Routes = [ { path: '', component: FirstComponent} ]; 

But, this time, through the forChild instruction (we will see the difference between forChild and forRoot later) 

imports: [ 
                RouterModule.forChild(routes) 
] 

Finally, make sure that FirstModule is not present in the AppModule’s import array.

To verify that you have configured everything correctly, we can start the application and open the browser developer tool, positioning us in the Network section. 

At this point, when we try to navigate to FirstComponent, a call similar to the one in the figure will appear. 

This call will be made only once for each module loaded via Lazy Loading, the first time we will access that path, in our case localhost:4200/first.

To conclude the argument, we must clarify the difference between forRoot and forChild. Let’s see why we used forRoot in the AppModule and forChild in the feature Modules. 

The RouterModule has its route management service; this service is a singleton and must be registered only once within the application.  

To give the possibility to register the service only once, the Angular team exposed the two static forRoot and forChild methods, which return a ModuleWithProviders interface. In Angular, you can also import a module via the ModuleWithPRoviders interface, which has the following structure:

interface ModuleWithProviders<T> {  
        ngModule: Type<T>  
        providers?: Provider[]  
} 

In this way, in addition to the module, the list of providers associated with it is also imported, so that they are registered in the dependency injection engine.

The forRoot returns a ModuleWithProviders object containing as ngModule the RouterModule, and in the list of providers returns the RouterService. forChild, instead, returns the same object but with the list of providers empty. This ensures that RouterService is only registered once. 

If you try to record RouterModule using the forRoot method, even in one of the feature modules, you get the following error in the console when you access the module’s path in question.

I hope that the topic has interested you and, if you are interested to deepen the use of this framework, I invite you to read the articles on Angular in our blog.

To the next article!