Configuring Lazy Loaded Angular Modules

Making our Angular modules configurable is an important step in building a reusable architecture. Having used Angular for a while you might be familiar with the commonly used forRoot() and forChild() functions, that some modules provide you with. But what is the best way to provide configuration in these cases?

In diesem Artikel:

Configuring Lazy Loaded Angular Modules
Yannick Baron ist Architekturberater bei Thinktecture mit einem Fokus auf Angular und RxJS.

The purpose of said functions are to initialize the module, e.g. providing services the module or application might need, and offering a way to provide a configuration for the module, that its components and services can use.

In such a forRoot() function, we might commonly see a snippet of code along the following lines:

				
					export interface ModuleConfig {
  debug: boolean;
  text: string;
}
​
export const MODULE_CONFIG = new InjectionToken<ModuleConfig>('Module Config');
​
export const DEFAULT_CONFIG: ModuleConfig = { debug: false, text: 'Eager Module' };
​
@NgModule({
  declarations: [HomeComponent],
  imports: [CommonModule, ConfigurableRoutingModule],
})
export class ConfigurableModule {
  static forRoot(config: Partial<ModuleConfig>): ModuleWithProviders<ConfigurableModule> {
    const mergedConfig = { ...DEFAULT_CONFIG, ...config };
​
    return {
      ngModule: ConfigurableModule,
      providers: [{ provide: MODULE_CONFIG, useValue: mergedConfig }],
    };
  }
}
				
			
This opens up the possibility to provide a config to the forRoot() function of the module. It is merely a static function that returns a representation of the Angular module with additional providers. We provide the module’s config via the injection token MODULE_CONFIG. ​ In this case this config is provided in the application’s root injector, thus making it accessible to the whole Application. Of course it is possible to provide the config in the AppModule directly resulting in the same behavior, however, it degrades the usability of our module, as the user needs to be aware that it is their responsibility to do so. Therefore, we prefer the above approach resulting in a clear API for the user.

Lazy Loaded Modules

As an Angular veteran you are probably familiar with lazy loading your modules. It usually looks like something like this:

				
					const routes: Routes = [
  {
    path: 'lazy',
    loadChildren: () =>
      import('../app/lazy-loaded/lazy-loaded.module').then(
        (m) => m.LazyLoadedModule,
      ),
  },
];
				
			
​ Angular expects us to provide a Promise that returns the module class. We cannot return the ModuleWithProviders as our forRoot() function does. Now imagine the situation that you do want to provide a config with the module, as we usually would want to do. One way of course is providing the config in the application root as outlined above. For the sake of a cleaner API, let’s try to make this module configurable. ​ One thought about lazily loaded modules: a module that is loaded during the runtime when the user navigates to a certain route, cannot possibly provide a service or configuration to the root level of the application. If that were the case, other parts of the app would not function properly unless the user navigated to the route first. ​ That means a config we provide for this module, will only be relevant to that module. This also comes with the added benefit, that this encapsulation will allow us to reuse the module on two different routes with different configurations.

Configuring Lazy Loaded Modules

We want to suggest a way to configure our lazy loaded module in a similar way as shown in the introduction to this article. ​

In order to make this possible we need to give our module a static method:

				
					@NgModule({
    // [...]
})
export class LazyLoadedConfigurableModule {
  static config = DEFAULT_CONFIG;
​
  static configure(
    config: Partial<ModuleConfig> = DEFAULT_CONFIG,
  ): LazyLoadedConfigurableModule {
    this.config = { ...DEFAULT_CONFIG, ...config };
    return this;
  }
}
				
			
Similarly to our forRoot() example, we provide a static method to receive and prepare our module’s config. Important to note are the two differences
  • the static method returns this (the module class)
  • we store the config in a static class variable
Using the method above we can now provide a config to our module when lazy loading as follows:
				
					const routes: Routes = [
  {
    path: 'lazy',
    loadChildren: () =>
      import('../app/lazy-loaded-configurable/lazy-loaded-configurable.module').then(
        (m) => m.LazyLoadedConfigurableModule.configure({ text: 'Lazy Loaded' }),
      ),
  },
];
				
			

There is now a missing piece to make this all work. While we do provide the config and could potentially access it via the static class member, we would like to get our config into the Angular DI. Leveraging a factory provider, we can do so as follows:

				
					function ConfigFactory() {
  return LazyLoadedConfigurableModule.config;
}
​
@NgModule({
  declarations: [HomeComponent],
  imports: [CommonModule, LazyLoadedConfigurableRoutingModule],
  providers: [
    {
      provide: MODULE_CONFIG,
      useFactory: ConfigFactory,
    },
  ],
})
export class LazyLoadedConfigurableModule {
  static config = DEFAULT_CONFIG;
​
  static configure(
    config: Partial<ModuleConfig> = DEFAULT_CONFIG,
  ): LazyLoadedConfigurableModule {
    this.config = { ...DEFAULT_CONFIG, ...config };
    return this;
  }
}
				
			

This will provide our config in Angular’s DI on module level and thus making our lazy loaded module configurable. We also maintain a clean developer experience and avoid the implicit knowledge necessary to provide a config in the app root for a module that might never be loaded in the first place.

Conclusion

Above we explored configuring a lazily loaded Angular module. If you are already familiar with making modules configurable, the solution introduced in this article should be easy to implement. In case you are seeing how to provide your modules with a forRoot method for the first time now, consider adding it to your tool-belt!

GitHub Repository

You can find a sample implementation of the above concept right in this GitHub Repository:
https://github.com/thinktecture/blog-configure-lazy-modules-demo

Kostenloser
Newsletter

Aktuelle Artikel, Screencasts, Webinare und Interviews unserer Experten für Sie

Verpassen Sie keine Inhalte zu Angular, .NET Core, Blazor, Azure und Kubernetes und melden Sie sich zu unserem kostenlosen monatlichen Dev-Newsletter an.

Diese Artikel könnten Sie interessieren
Angular
Managing Your Collections With the EntityAdapter: @ngrx/entity-Series – Part 1

Managing Your Collections With the EntityAdapter: @ngrx/entity-Series – Part 1

This three-part series of blogposts is targeted at developers who have already gained experience with NgRx but still manage their collections themselves. In the first part I introduce the Entity Adapter, in the second part I show you how to connect it to NgRx and in the third part how to do it with the Component Store as well.
31.01.2023
Angular
Implementing Smart and Presentational Components with Angular: Condensed Angular Experiences – Part 4

Implementing Smart and Presentational Components with Angular: Condensed Angular Experiences – Part 4

In this article, we will explore how to apply the concept of smart and presentational components with Angular. We will choose a complex-enough target to see all aspects in action, yet understandable and within the scope of this article. The goal is to teach you how to use this architecture in your way. For that, we will iterate through different development stages, starting with the target selection and implementing it in a naive way. After the first development, we will refactor that naive solution into smart and presentational components that are reusable, refactor-friendly, and testable.
23.01.2023
Angular
Angular OnPush – A Change Detection Strategy Revealing Mistakes in Your Code

Angular OnPush – A Change Detection Strategy Revealing Mistakes in Your Code

When optimizing the performance of Angular applications, many developers directly associate Angular's OnPush change detection strategy with it. But, if you don't know exactly how OnPush works under the hood, it will quickly teach you what you are doing wrong the hard way. In this article, we'll look deeper into how the OnPush strategy affects Angular's change detection mechanism and which pitfalls we should avoid at all costs.
24.10.2022
Angular
Master Web Component Forms Integration – with Lit and Angular

Master Web Component Forms Integration – with Lit and Angular

When a company has cross-framework teams, it is a good choice to use Web Components to build a unified and framework-independent component library. However, some pitfalls are to consider when integrating these components into web forms. Therefore, for a better understanding, we will look at two possible approaches and try to integrate them into an Angular form as an example.

Notice: All code samples are available on Github!
09.06.2022
.NET
Asynchrone Operationen: Blazor WebAssembly für Angular-Entwickler – Teil 5 [Screencast]

Asynchrone Operationen: Blazor WebAssembly für Angular-Entwickler – Teil 5 [Screencast]

Eine Webanwendung will natürlich auch mit Daten gefüttert werden. Doch diese müssen irgendwo her kommen. Nichts liegt näher als diese von einer Web API zu laden. Dieser Screencast zeigt, wie asynchrone Operationen in Blazor funktionieren und welche gravierenden Unterschiede es zu Angular gibt.
26.05.2022
.NET
Typings: Blazor WebAssembly für Angular-Entwickler – Teil 4 [Screencast]

Typings: Blazor WebAssembly für Angular-Entwickler – Teil 4 [Screencast]

C# und TypeScript entstammen der Feder der selben Person. Doch sind sie deshalb auch gleich? In diesem Teil der Screencast-Serie erfahren Sie, wie mit Typen in den beiden Programmiersprachen verfahren wird und welche Unterschiede es gibt.
19.05.2022