Previous month:
February 2017

May 2017

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.

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

Did you ever use template reference variables in Angular? Maybe you don't remember exactly what they are or represent. Quick and easy they allow you to access either a DOM element or an instance of a component or directive in your current template. They are defined by writing a hash sign (#) together with a name as an additional attribute of the DOM element. If this DOM element is just a plain one the variable contains a reference to it. In that case you can access any property of that DOM element through this variable:

<input type="text" (change)="true" #variable>
{{ variable.value }}

In this example the entered value will be written out when you blur the input (the event listener for change is just for having the change detection run).

If you have a component living on the DOM element the variable will contain the instance of that component (and not the DOM element):

<my-component #variable [input]="'hello'"></my-component>
{{ variable.input }}

And if you want to access the instance of a directive you need to specify which one, because there could be more than one directive existing on that DOM element. The exportAs property of the directive's decorator defines the attribute's value to access that particular instance. For example, the FormsModule's directive for the form DOM element (NgForm) is exporting itself as ngForm. This enables you to e.g. disable a button until the form is valid:

<form #variable="ngForm">
  <!-- some more HTML ... -->
  <button type="submit" [disabled]="variable.form.invalid">Submit!</button>

Most of the time this works without any problems. But there is a snag in it. First let's take a look what the Angular documentation writes about these variables:

"You can refer to a template reference variable anywhere in the template."

Sounds good. And later the documentation states:

"The scope of a reference variable is the entire template."

Yep, got it. But take a look at the following component's template:

<div *ngIf="true">
  <my-component #variable [input]="'Do you see me?'>
{{ variable.input }}

What do you think is happening now? Will it show the component input's value "Do you see me?" on the screen? No! It won't. Instead you will get an error in the console:

TypeError: Cannot read property 'input' of undefined

Wait! Didn't the documentation suggest multiple times that these variables can be used anywhere in the template? Yes, and that is still true even it does not look like that for now.

The reason for this is the ngIf directive - or any other directive used together with a star (*). These directives are so called structural directives and the star is just a short form for something more complex. Every time you use a structural directive (ngIf, ngFor, ngSwitchCase, ...) Angular's view engine is desugaring (it removes the syntactical sugar) the star within two steps to the following final form (using the previous ngIf as an example):

<ng-template [ngIf]="true">

Did you notice? Something extraordinary emerged out of the previous HTML containing the structural directive - the ng-template node. This node is the reason for limiting the scope of the template reference variable to exactly this inner DOM part only - it defines a new template inside. Because of limiting a variable to its defining template any variable defined within the ng-template (and this means within the DOM part of the ngIf) cannot be used outside of it. It cannot cross the borders of a template.

Huh, so the documentation is right? Yes, but maybe it is not clearly written somewhere that structural directives introduce new nested templates with new barriers for those variables which should be available in the whole template file primarily.