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
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
.NET
Bindings: Blazor WebAssembly für Angular-Entwickler – Teil 3 [Screencast]

Bindings: Blazor WebAssembly für Angular-Entwickler – Teil 3 [Screencast]

Wer Komponenten einsetzt, steht früher oder später vor der Fragestellung, wie man Daten an die Komponente übergibt oder auf Ereignisse einer Komponente reagiert. In diesem Screencast wird gezeigt wie Bindings bei Komponenten funktionieren, also wie eine Komponente Daten von außerhalb benutzen und Rückmeldung bei Aktionen geben kann.
12.05.2022
.NET
Komponenten: Blazor WebAssembly für Angular-Entwickler – Teil 2 [Screencast]

Komponenten: Blazor WebAssembly für Angular-Entwickler – Teil 2 [Screencast]

Bei der Entwicklung einer Webapplikation kommt es ständig vor, dass UI-Teile immer und immer wieder verwendet werden. Damit nicht immer Copy & Paste verwendet werden muss, können diese Teile in Komponenten zusammengefasst werden.
05.05.2022
.NET
Intro: Blazor WebAssembly für Angular-Entwickler – Teil 1 [Screencast]

Intro: Blazor WebAssembly für Angular-Entwickler – Teil 1 [Screencast]

Jahrelang gab es kein Framework von Microsoft, mit dem es möglich war, webbasierte Cross Plattform Applikationen zu erstellen. Und dann kam Blazor WebAssembly. Im ersten Teil dieser Screencast-Serie wird erklärt, worum es sich bei Blazor WebAssembly handelt.
05.05.2022