Sharing Angular modules like a boss

What forRoot and forChild have to do with lazy loading

You just created a beautiful Angular component. Now, being the exemplary developer that you are, you want to share it with others.

In Angular, you achieve this by moving it to a separate NgModule. The next step is to make your Angular module customizable. Dependency injection is the key to success!

However, be careful with lazy loading.

Please enjoy this walkthrough and these tips and tricks for configuration in Angular. Let’s make it awesome!

Step 1: the FooModule

Imagine a simple FooModule, with a FooComponent:

The reason we added the FooComponent to the exports array, is that other modules that import this module, can now use the FooComponent as well.

Now imagine that the FooComponent shows a label with a prefix:

Of course, this is a very simplified example, but you get the idea. This component can now be used as:

Let’s add some configuration.

Step 2: adding configuration

The second step is to make the prefixconfigurable.

Of course we could just add an input property binding with @Input, like we did for label. However, imagine that we want to configure the prefix globally for every FooComponent.

A good way to achieve this, is with a configuration class and dependency injection:

This FooConfig can be a configuration class, e.g.:

If you want to use a TypeScript interface instead, you have to use an InjectionToken. This is because TypeScript interfaces do not exist at runtime.

Let’s provide a default config in our FooModule so that anyone can use this module right away:

Anyone importing this FooModule will use the default config. It is also possible to provide a custom configuration (if preferred):

Any FooComponent in this Angular will now use the custom prefix.

Step 3: use in example application

Imagine an app with the following routing structure:

  • AppModule (root module)
    • DashboardModule (route “/dashboard”)
    • UsersModule (route “/users”)

Now, when both DashboardModule and UsersModule need to render FooComponents, they should also both import the FooModule:

However, we still want to have a global configuration, so we keep providing the FooConfig in the root module:

This works fine, because the injector of AppModule is the top-most injector for every other module.

Step 4: optimise for lazy-loading

Now, imagine that the UsersModule of the previous example app is loaded lazily:

This creates an issue: the UsersModule – which is loaded lazily – will not inherit the custom config from the root module, but the default config from the FooModule.

What happened?

It seems that a module which is lazily loaded will be the top-most injector at bootstrap time.

To solve this, we have to:

  • Import FooModule WITH a default config in root module (which we can optionally override)
  • Import FooModule WITHOUT a default config in child modules (in order to use its FooComponent)

Instead of making two different modules, Angular provides a ModuleWithProviders interface for this:

Now we can use the fooModuleWithConfig in our root module, and fooModuleWithoutConfig in other modules:

This will solve our issue. While overriding the default config is still possible.

Step 5: stick to conventions

In order to make the intended use more visible, it is recommended to use the static methods forRoot() and forChild():

An added benefit of this is that we can even require a configuration object as argument for the forRoot() method (like above), or merge it with a default one.

Final use

In the root module:

In any (lazy-loaded) child module:

Note: As .forChild() in this case does not return any providers at all, you can also just use the “plain” FooModule in child modules:

Code examples

Sharing is caring: go for it! 

Een reactie plaatsen

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *