[Outdated]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:

yb
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.

Newsletter Anmeldung
Diese Artikel könnten Sie interessieren
Angular
SL-rund

View Transition API Integration in Angular—a brave new world (Part 1)

If you previously wanted to integrate view transitions into your Angular application, this was only possible in a very cumbersome way that needed a lot of detailed knowledge about Angular internals. Now, Angular 17 introduced a feature to integrate the View Transition API with the router. In this two-part series, we will look at how to leverage the feature for route transitions and how we could use it for single-page animations.
15.04.2024
Low-angle photography of metal structure
AI
cl-neu

AI-Funktionen zu Angular-Apps hinzufügen: lokal und offlinefähig

Künstliche Intelligenz (KI) ist spätestens seit der Veröffentlichung von ChatGPT in aller Munde. Wit WebLLM können Sie einen KI-Chatbot in Ihre eigenen Angular-Anwendungen integrieren. Wie das funktioniert und welche Vor- und Nachteile WebLLM hat, lesen Sie hier.
26.02.2024
Angular
SL-rund

Konfiguration von Lazy Loaded Angular Modulen

Die Konfigurierbarkeit unserer Angular-Module ist für den Aufbau einer wiederverwendbaren Architektur unerlässlich. Aber in der jüngsten Vergangenheit hat uns Angular seine neue modullose Zukunft präsentiert. Wie sieht das Ganze jetzt aus? Wie konfigurieren wir jetzt unsere Lazy-Komponenten? Lasst uns gemeinsam einen Blick darauf werfen.
03.08.2023
Angular
yb

Using EntityAdapter with ComponentStore: @ngrx/entity Series – Part 3

As someone who enjoys the ComponentStore on an average level, I have written simple reactive CRUD logic several times. While storing a vast number of entities in the component state might not be a frequent use case, I will briefly illustrate the usage of the EntityAdapter with the @ngrx/component-store.
14.02.2023
Angular
yb

Multiple Entity Collections in the Same Feature State: @ngrx/entity-Series – Part 2

After introducing the @ngrx/entity package, I am often asked how to manage multiple entity types in the same feature state. While I hope that the previous part of this article series has made this more apparent, I will further focus on this question in the following.
07.02.2023
Angular
yb

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