Angular Modules and Lazy Loading

angular modules lazy loading

This article on Angular Modules & Lazy Loading is part of the Learning Angular series.

Understanding the App Module

Imports are a language feature that tells the compiler to where to look for the files and features that are required.
Modules are a part of Angular which it sees as a way of binding multiple things together.
Modules are a bunch of codes bundled together and imported as a single statement in the imports array, thus minizing the number of codes that are written in the AppModule.
Everything provided in providers array in the AppModule gives a single instance on which the whole app works.
Bootstrap array defines which is the root element of the application.

Understanding Feature Modules

If a particular part/feature of the ap can be fully outsourced to another library, t can be craeted as a feature module.
In the current aplication we are building, the recipes and its related features can make up a feature module.

Creating a Recipe Feature Module

CommonModule needs to be imported in almost all feature modules because it gived access to the Angular build-in modules like the ngClass.
The AppModule by default doesn’t have the CommonModule but in place has the BrowserModule. This is because the BrowserModule contains the CommonModule as well as some addtional modules that are required to run the application in the browser.

Registering Routes in a Feature Module

To register routes in the FeatureModule, we need to register our routes in the RouterModule. Thisc an be done by using the RouterModule.forChild function.

imports: [
  RouterModule.forChild(featureModuleRoutes)
]
exports: [ RouterModule ]

While registering Routes in the AppModule , one needs to use the RouterModule.forRoot functionality, but in any other module , one needs to use the RouterModule.forChild property
One thing to notice is that the Dropdown Directive won’t be working in the FeatureModule even if it is provided in the AppModule, because Angular doesn’t know if it has to use that in the FeatureModule.
We can’t import the Directive in both the AppModule and the FeatureModule because Angular will complain about it being imported in multiple modules.

TODO ISSUE – ** in the AppRoutingModule creates problem when browsing routes defined in the FeatureModule.

Lecture 273 – Understanding Shared Modules

When any module needs to be used across multiple modules, it should be put into a SharedModule.

Creating a Shared Module

While there can be multiple shared modules in an application, typically there is only one.
We use the declarations and the imports in the SharedModule and export the ones which we need to share around the application.

@NgModule({
  declarations: [
    DropdownDirective
  ],
  imports: [
    ... // Not required mostly
  ],
  exports: [
    CommonModule,
    DropdownDirective
  ]
})
export class MySharedModule { }

Now importing this module into other modules will make sure that the common componets/directives are available throughout the applciation without having to import them multiple times.

Creating as Shopping List Feature Module

In the case of Shopping List Module, we didn’t move the route to its own module as because there was just one route that needed to be moved and it doesn’t make sense to create another file for it. Details will be given in the next module.

Loading Component via Selectors vs Routing

When we use selectors for vieweing a component, we need to delcare that component in the AppModule, but in the case of routing (using router-outlet), one doesn’t need to declare the component in the same module, but just needs to be initialized before getting used.

A Common Gotcha

Normally if we remove a module that is being used in the AppModule components, we can get errors. While we can export the required module from another modulke being used in the application, it isn’t the right way of doing it.

Creating the Auth Feature Module

Understanding Lazy Loading

In a normal ANgular application, all the code gets downlaoded at the beginning. SO it swould also download many FeatureModules which will never be visited by the user.
Lazy loading in ANgular makes sure that the Feature Modules and the associated routes are only loaded when the user actually visits any of those pages.

Adding Lazy Loading to the Recipes Module

To make the RecipesModule lazy loaded, we will first need to get a default HomePage as the current setup redirects the user to the recipes page by default.
First remove the module from the imports array of the AppModule, ebcause anything provided there will be loaded at runtiume.
Then in the AppRoutingModule, add a path for the recipes,

{ path: 'recipes', loadChildren: './recipes/recipes.module#RecipesModule' }

Normally in the routes, we provide the component declaration for the path, but for lazy loading, we need to pass the loadChildren attribute, along with the path to the module as a string. Also attach the Module name at the end preceded by a #.
Now to make sure that this works, we need to remove the ‘recipes’ route from the RecipesRoutingModule, and make it an empty path.

Protecting Lazy Loaded Routes with canLoad

What if you want to use route protection (canActivate to be precise) on lazily loaded routes?
You can add canActivate to the lazy loaded routes but that of course means, that you might load code which in the end can’t get accessed anyways. It would be better to check that BEFORE loading the code.
You can enforce this behavior by adding the canLoad guard to the route which points to the lazily loaded module:

{ path: 'recipes', loadChildren: './recipes/recipes.module#RecipesModule', canLoad: [AuthGuard] }

In this example, the AuthGuard should implement the CanLoad interface.

How Modules and Services Work Together

– REVISIT
When we provide a service to both the AppModule and a FeatureModule (not lazily loaded), there is only one instance of the service created as becaseu Angular creates only One Root Injector during the start of the aplpication, and so only one instance of the service will be created.
So if you access the service anywehere including the laziliy loaded module where the service hasn’t been provided, each one will get a single isntance.

In case where the service is also provided in the lazily loaded module, another instance of the servbice will be created and forced into the Child Injector created by Angular. So one will get a different intance oft he service in the lazily loaded module than that of the AppModule or any other intancly loaded modules.
One can also create different instance of asevice at a acompn\oment level by providing the sevice at the component declaration rather than the Module declaration.

In a case where a SharedModule is created and the service is injected into the SharedModule. Then if the SharedModule is imported to non-lazily loaded module as well as a lazily loaded module, one would assume that bnoth the mpdules will sahre the same service. BUt in relaity, the non-lazily module will get the instance from the Root Injector in the AppModule, whereas for the laily loaded module, a new Child Injector would be created. This is because providing the service in a SharedMModule and providing the module to the lazily loaded module is same to that of providing thse service directly to the module.
So this is one of the major reasons, why a servbice should never be provided in a SharedModule. It would be quite difficult ot figure out what one is going to get with this setup.
Note: Don’t priovide a service in a Shared Module. Specially when you plan to use them in Lazy Loaded Modules.

Understanding the Core Modules

There are some parts of the application that are required through the lifetime of the aplication, like a header.
Components like this form the core of the applciation and should be moved to a Core Module to make it easy for managing the AppModuole.

Creating a Basic Core Module

The header and the home components and any other components/directives that are part of the AppCOmponent can be moved to the Core Module.
Created a core folder and move all the required stuff in there and create the CoreModule and export the required elements out of the COre Module.
NOrmally you should only have the AppCOmponent ion the AppModule declarations array.

Restructing Services to use the Child Injector

All the services in the providers array of the AppModule can also be moved to the CoreModule as because it is anyway going to be bootstrapped to the Root Injector of the app.
In the currenl application, the AuthGurad can be specifally moved to the recipes-routing.module becaseue it si the only place where it is being used.
This would make the code more leaner and also make it a little fastera s all the Auth related items get moved to the lazlily loaded module.
AuthGurad is the only service mofule wchih needs to go in the Routing Module. ALl other serbices module shoudl go directly into the feature module. It’s not required but is a better structure to use it.

Using Ahead-of-Time Compilation

There are 2 types of compilation procedures angular follows. Just-in-Time compilation and Ahead-of-Time compilation.
Compilation is a process by which Angular converts our template files to javacript keeping the dynamicity itact. This is made to sure that the rendering is fast as it is earier to manipulate javacript and render it, that manipulating the HTML DOM directly.
In the case of Just-in-Time compilation, when the application code is downloaded in the browser, Angular compiles the code there to javacript, thus making the rendering a little slow.
In the case of AOT comipaltion, Angular compiles the code when we build it for production. This saves us the time that Angular takes when compiling the code at runtime in the browser. This makes the application startp faster.
We also save applciation size as the compiler code is not shipped with our code in the produced build. Also Angular strips out unused code from the build when we run the AOT compilation. FOex example, if we don’;t use the ngIF statement anywhere, Angular will remove the total functioanlity of ngIf from the code.
One more advantage is that all the templates get checked during development and one can fix the error before it appears in the production environment.

How to use AOT Compilation with the CLI

ng build --prod --aot

–prod minifies the code but doesn’t use AOT (most probably it has started doing so, need to check)
–aot compiles the code in the AOT mode,making the vendor.js and main.js files very small.

Preloading Lazy Loaded Routes

The implementation of Lazy Laoading also introduces a mninor problem that when we visit the to be Lazy Loaded module, it takes some time to download the module and the app may hand for that moment of time. To ake sure that this doesn’t happen, nbut the Lazy Loaded modules do get downloaded at a later point of time of downloading the main bundle, we can use the pre loading lazy loaded modules strategy. To do this,
app-routing.module.ts

imports: [
  RouterModule.forRoot(appRoutes, {preloadingStrategy: PreloadAllModules})
]

PreloadAllModules need to be imported from @angular/router and is the strategy to make sure thatthe lazy loaded modules get preloaded before their time.

Leave a Reply