DEV Community

Madhu Sudhanan P
Madhu Sudhanan P

Posted on • Originally published at towardsdev.com

2 1

Lazy loading non-routable Angular modules — Imperative & Declarative pattern

Image description
Lazy loading modules in angular enable us to split code into separate modules and load them on demand, this helps developers to have maintainable code and better performance.

By default, angular lazily load modules based on the route path. Lazy loading modules based on the route path suits most cases but sometimes you might want to load a module irrespective of the route path. Some of those cases are as follows.

  • Load on button click without route change.
  • Load multiple modules for the current route. In such a case one module can be loaded based on the router & others can be loaded manually.
  • Dynamically insert components on some criteria.

In this post, I am going to show, how to lazily load modules manually in both imperative & declarative (less imperative) ways. Following the declarative way is important when comes to frameworks since it ensures whether the application has less code and is unit testable.

In terms of frameworks, simply being declarative is leaving the framework to handle things by declaring required syntaxes in the template (as I understand). When I started to learn the differences between imperative and declarative ways, I understand that writing things in the *component.ts file will make you more imperative.

Let’s get started!!

Imperative

The below code loads modules and renders them into the view imperatively.

<ng-container #placeholder></ng-container>
import {
Compiler,
Component,
ComponentFactory,
Injector,
Input,
OnInit,
Type,
ViewChild,
ViewContainerRef } from '@angular/core';
import { LoadChildrenCallback } from '@angular/router';
import { from, Observable, throwError } from 'rxjs';
import { map, switchMap, catchError } from 'rxjs/operators';
@Component({
selector: 'app-lazy-load-imperative',
templateUrl: './lazy-load-imperative.component.html',
styleUrls: ['./lazy-load-imperative.component.sass']
})
export class LazyLoadImperativeComponent implements OnInit {
@ViewChild('placeholder', { read: ViewContainerRef, static: true })
public container: ViewContainerRef | undefined;
@Input()
load: LoadChildrenCallback | undefined;
component: Type<any> | undefined;
constructor(private compiler: Compiler,
private injector: Injector) { }
ngOnInit(): void {
this.loadModule().subscribe(componentRef => this.container?.createComponent(componentRef));
}
loadModule(): Observable<ComponentFactory<any>> {
return from(Promise.resolve((this.load as LoadChildrenCallback)()))
.pipe(
switchMap(module => {
this.component = module.rootComponent;
return this.compiler.compileModuleAsync(module as Type<unknown>);
}),
map(factory => factory.create(this.injector)),
map(factoryRef => factoryRef.componentFactoryResolver.resolveComponentFactory(<Type<any>>this.component)),
catchError(err => { console.error(err); return throwError(err); })
)
}
}

The reasons which make this imperative are as follows.

  • The first thing that makes this code imperative is, manually subscribing to the loadModule method.
  • Next, I see we are compiling the module, registering the injector & resolving the component.
  • Appending the component in the ng-container using the ViewContainerRef.

Declarative (Less imperative)

Now let’s see the declarative version of the above component and template file. Also, I would say it is less imperative instead of declarative since I am using some RxJS operators & manually loading the module using load property.

<ng-container *ngComponentOutlet="component | async"></ng-container>
import { Component, Input, OnInit, Type } from '@angular/core';
import { LoadChildrenCallback } from '@angular/router';
import { from, Observable } from 'rxjs';
import { switchMap } from 'rxjs/operators';
@Component({
selector: 'app-lazy-load-declarative',
templateUrl: './lazy-load-declarative.component.html',
styleUrls: ['./lazy-load-declarative.component.sass']
})
export class LazyLoadDeclarativeComponent implements OnInit {
@Input()
load: LoadChildrenCallback | undefined;
component?: Observable<Type<any>>;
ngOnInit(): void {
this.component = this.loadModule();
}
loadModule(): Observable<Type<any>> {
return from(Promise.resolve((this.load as LoadChildrenCallback)()))
.pipe(
switchMap(module => from(Promise.resolve(module.rootComponent)))
)
}
}

Well, I believe you can see the difference here.

  • First thing, lines of code are reduced.
  • Removed injection of Compiler.
  • Removed manual compiling, injection & component resolving.
  • Removed manual appending of the component using ViewContainerRef.

Thanks to Angular!. All the above things comes possible because of the introduction of the ngComponentOutletdirective. In the above code, I am simply loading the module & resolving the component property after the module loaded successfully.

Wrap it up

Out there, I can see there are many blog posts showing different ways to lazy load non-routable modules in angular and you can choose them based on your application scenarios.
You can find the full source code in this GitHub repo. I hope, you have learned something from this blog as I did.

Thanks for reading. Happy coding. Feel free to contact me if any.

SurveyJS custom survey software

JavaScript UI Libraries for Surveys and Forms

SurveyJS lets you build a JSON-based form management system that integrates with any backend, giving you full control over your data and no user limits. Includes support for custom question types, skip logic, integrated CCS editor, PDF export, real-time analytics & more.

Learn more

Top comments (0)

SurveyJS custom survey software

JavaScript Form Builder UI Component

Generate dynamic JSON-driven forms directly in your JavaScript app (Angular, React, Vue.js, jQuery) with a fully customizable drag-and-drop form builder. Easily integrate with any backend system and retain full ownership over your data, with no user or form submission limits.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay