Use Angular template reference variables anywhere in the template... not!

The real secrets behind forRoot and/or forChild of an NgModule

Sometimes you may stumble upon an NgModule which exposes a forRoot() method on its type. This can have a couple of reasons, and one of them should be really important to you.

If the method has parameters, you may have the possibility to provide some configuration. For instance, the RouterModule allows the developer to declare their routes using this method. The interesting thing is, that this is also the only well-known reason for those methods to exist. But that's not the truth. There is something even more important to know about these methods.

First, let's have a look at the return type of the method: ModuleWithProviders

What is the Angular API documentation writing about this type?

A wrapper around a module that also includes the providers.

That's all? Not much, though. So what is the mystery behind this? To reveal this, we have to look a little bit deeper into the way the Angular Router is handling a lazy loaded module (with the loadChildren route property defined). Normally, an imported module uses the top-most injector of the application. This means, if you don't lazy load any modules, the AppInjector is the top-most injector for every module. In that case, when Angular is crawling the modules and their providers through the imports array in the NgModule decorator, all providers get collected in this one injector and, as usually, the last one declared wins.

That is not happening when the module gets lazy loaded. Because of that, Angular can't crawl this module during bootstrap and nothing is known about any providers declared in the providers array of the NgModule decorator. When the module is then loaded, Angular has no chance but to create another injector which is (for a short time) the top-most for that part of the application. The reason for this is that a configured injector cannot be changed afterwards. Of course, if anything is not declared in that new (nested) injector the lookup continues in the parent injector. When you use the providers array in the Component decorator the same thing is happening as well.

Closing the bridge, the ModuleWithProviders type has also a providers array in its definition. If you import your module anywhere in the main application and in lazy loaded modules, its NgModule decorator should not have any providers listed to prevent another instantiation of them. Instead, the module should implement a forRoot() method returning these providers. You are then using this method to import the module in your app module once. At that point the providers get registered into the AppInjector and are available for injection. Every time you are going to import the module by type, no providers are defined in your NgModule decorator and won't get registered another time in a lazy loaded module's injector.

In my opinion this is a much more important secret behind the forRoot() or forChild() convention to bear in mind.

Comments

Feed You can follow this conversation by subscribing to the comment feed for this post.

The comments to this entry are closed.