Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at cf.Objective() 2013 (Bloomington, MN) with: Gert Franz and Aaron Greenlee
Ben Nadel at cf.Objective() 2013 (Bloomington, MN) with: Gert Franz@gert_railo ) and Aaron Greenlee@aarongreenlee )

NgModule Constructors Provide A Module-Level Run Block In Angular 2.1.1

By Ben Nadel on

Last night, Ward Bell said something to me that sort of blew my mind. In retrospect, what he said is totally obvious; but, it was something that I had never considered before. He mentioned that "configuration" can be performed in the NgModule constructor. To date, I've never really considered the NgModule class itself; I mostly viewed it as a hook on which to hang the @NgModule() meta-data. But, now that I think about it, the NgModule constructor provides an implicit module-level "run block" in your Angular 2 application.


 
 
 

 
 
 
 
 

Run this demo in my JavaScript Demos project on GitHub.

Recently, I've been thinking a lot about configuration in an Angular 2 application. Between dependency-injection and run blocks, we have a lot of options. And, with the NgModule constructor, it seems like we have one more. To explore this new avenue of configuration, I wanted to put together a simple demo that simply logs messages to the console so that we can see the order-of-operations during the bootstrapping process.

First, I'm creating a non-root module - OtherModule - that provides a service - OtherService. This module will ultimately be imported into the root module; but, I wanted to see how each NgModule instance would operate on its own. So, I gave the OtherModule a constructor and tried to dependency-inject its service:

  • // Import the core angular services.
  • import { NgModule } from "@angular/core";
  •  
  •  
  • // I am just defining a class here so that I can add to the Providers collection and
  • // then inject it into the Module to see how all the constructors work.
  • // --
  • // NOTE: We are exporting this class, which is subsequently imported into the AppModule
  • // for dependency-injection into the AppModule constructor.
  • export class OtherService {
  •  
  • constructor() {
  •  
  • console.log( "OtherService constructor." );
  •  
  • }
  •  
  • }
  •  
  •  
  • @NgModule({
  • providers: [ OtherService ]
  • })
  • export class OtherModule {
  •  
  • // I initialize the module.
  • constructor( otherSerivce: OtherService ) {
  •  
  • console.group( "OtherModule Constructor." );
  • console.log( otherSerivce );
  • console.groupEnd();
  •  
  • }
  •  
  • }

As you can see, I'm just injecting the OtherService into the OtherModule constructor and then logging it to the console.

Now, in the root module - AppModule - I'm doing the same thing. I'm providing a service - AppService - and injecting it into the AppModule constructor. Only this time, I'm also injecting the OtherService imported from the OtherModule:

  • // Import the core angular services.
  • import { APP_INITIALIZER } from "@angular/core";
  • import { BrowserModule } from "@angular/platform-browser";
  • import { NgModule } from "@angular/core";
  •  
  • // Import the application components and services.
  • import { AppComponent } from "./app.component";
  • import { OtherModule } from "./other.module";
  • import { OtherService } from "./other.module";
  •  
  •  
  • // I am just defining a class here so that I can add to the Providers collection and
  • // then inject it into the Module to see how all the constructors work.
  • export class AppService {
  •  
  • constructor() {
  •  
  • console.log( "AppService constructor." );
  •  
  • }
  •  
  • }
  •  
  •  
  • @NgModule({
  • bootstrap: [ AppComponent ],
  • imports: [ BrowserModule, OtherModule ],
  • providers: [
  • AppService,
  •  
  • // Here, we're also providing a "run block" which is a function that will execute
  • // at the very end of the bootstrapping process, right before the root component
  • // is instantiated.
  • {
  • provide: APP_INITIALIZER,
  • multi: true,
  • deps: [],
  • useFactory: function() {
  •  
  • return( runblock );
  •  
  • function runblock() {
  •  
  • console.group( "Run Blocks." );
  • console.log( "App module run block." );
  • console.groupEnd();
  •  
  • }
  •  
  • }
  • }
  • ],
  • declarations: [ AppComponent ]
  • })
  • export class AppModule {
  •  
  • // I initialize the module.
  • constructor( appService: AppService, otherService: OtherService ) {
  •  
  • console.group( "AppModule Constructor." );
  • console.log( appService );
  • console.log( otherService );
  • console.groupEnd();
  •  
  • }
  •  
  • }

As you can see, in the AppModule, I'm injecting both the AppService and the OtherService into the AppModule constructor. I'm also providing a traditional "run block" using the APP_INITIALIZER token.

Finally, in our root component, I'm just logging the constructor. This should be the very last thing that runs in the entire application:

  • // Import the core angular services.
  • import { Component } from "@angular/core";
  •  
  • @Component({
  • selector: "my-app",
  • template:
  • `
  • <em>Look in the console for the magic</em>!
  • `
  • })
  • export class AppComponent {
  •  
  • // I initialize the component.
  • constructor() {
  •  
  • console.log( "AppComponent constructor." );
  •  
  • }
  •  
  • }

Now, when we run this Angular 2 application, we get the following page output:


 
 
 

 
 NgModule constructor functions provide implicit module-level run blocks for configuraiton in an Angular 2 application. 
 
 
 

The first thing we notice here is that the NgModule constructors fired first. They fired before the "run block" and before the AppComponent constructor. We can also see that each NgModule constructor has access to the services that it provides. And, as you go up the "import tree", each module constructor has access to the services provided by the modules that it has imported. In this way, each NgModule constructor is acting as an implicit "run block" for the module itself. And, the root module's constructor acts as an implicit "run block" for the entire application.

This is really cool. Like I said above, in retrospect, this is kind of obvious. But, until last night, I had only ever considered NgModule in terms of meta-data, not in terms of "execution". After the meta-data is consumed, however, each NgModule class is instantiated with a constructor that can leverage Angular's dependency-injection (DI) mechanics. As such, these module constructors provide implicit run block functionality.




Reader Comments

Valuable info, thanks.

Have you considered writing about the "forRoot" convention, where the root module grabs the providers from child-modules, and integrating that into the article?

Reply to this Comment

@Lars,

To be honest, I don't have a great understanding of the .forRoot() convention. I know that I've used it for the Router (I think) and for something else (I can't remember); but I don't have a great mental model for how modules really fit together just yet, especially since all of my sample / test code basically only has one Module.

I've been diving into Node.js a bunch lately (for work); so, I'll hope to swing back into Angular again shortly.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.