Skip to main content
Ben Nadel at Scotch On The Rock (SOTR) 2010 (London) with: Adam Cameron
Ben Nadel at Scotch On The Rock (SOTR) 2010 (London) with: Adam Cameron

Configuring A Service With Both Dependency-Injection And A Run Block In Angular 2.1.1

By
Published in Comments (3)

Yesterday, I talked about translating the Angular 1.x "config phase" in an Angular 2 application by using dependency-injection. I also alluded to the fact that you could explicitly instantiate services using factory functions. Both of those approaches deal with instantiating services; but, we can continue to "configure" a service after instantiation using an Angular 2 "run block" at the end of the application bootstrapping.

Run this demo in my JavaScript Demos project on GitHub.

To be clear, I am not suggesting that you should arbitrarily spread configuration out over multiple parts of the application bootstrapping process. But, there may very well be times when configuration needs to be done in multiple places. For example, you might enable logging as a method-call on a service after it has been instantiated by Angular. Such a method invocation could be done in a run block.

But, more than anything, I just want to demonstrate what is possible so that you can be educated in how you approach configuration in an Angular 2 application. And, to do so, I'm going to revisit the Greeter concept from yesterday.

In my last post, the Greeter service worked by reducing a collection of "Greet Transformers" down into a single value - the greeting. In this post, the same is true; however, rather than supplying all of the transformers as part of a single multi:true dependency-injection token, I'm going to supply some via dependency-injection and one via a "run block" after the Greeter service has been instantiated.

To prepare for this, I'm going to rework my Greeter service to allow transformers to be provided to the Greeter both as part of constructor invocation and subsequently with an .addTransformer() method call:

// Import the core angular services.
import { Inject } from "@angular/core";
import { OpaqueToken } from "@angular/core";

// Import the application components and services.
import { IGreetTransformer } from "./transformers";


// I am the dependency-injection token that can be used to aggregate greet transformers.
// This is the collection that will be injected into the Greeter class during application
// bootstrapping. This kind of "multi" collection replaces the concept of a configuration
// phase in Angular 1.
export var GREETER_TRANSFORMERS = new OpaqueToken( "Injection token for Greet transformers." );


// I provide a service for generating greeting messages.
export class Greeter {

	private transformers: IGreetTransformer[];


	// I initialize the service.
	constructor( @Inject( GREETER_TRANSFORMERS ) transformers: IGreetTransformer[] ) {

		this.transformers = transformers;

	}


	// ---
	// PUBLIC METHODS.
	// ---


	// I add the given transformer to the greeter, if it's not already in use.
	public addTransformer( newTransformer: IGreetTransformer ) : void {

		if ( ! this.hasTransformer( newTransformer ) ) {

			this.transformers.push( newTransformer );

		}

	}


	// I return the greeting for the given name.
	public greet( name: string ) : string {

		var greeting = this.transformers.reduce(
			( reduction: string, transformer: IGreetTransformer ) : string => {

				return( transformer.transform( reduction ) );

			},
			name
		);

		return( greeting );

	}


	// I determine if the given transformer is being used by the greeter.
	public hasTransformer( transformer: IGreetTransformer ) : boolean {

		return( this.transformers.includes( transformer ) );

	}

}

As you can see, the .addTransformer() just turns around and pushes the value onto the existing transformers collection. Now, in my Greeter Module, I'm still going to initialize the dependency-injection token with the core greet transformer:

// Import the core angular services.
import { NgModule } from "@angular/core";

// Import the application components and services.
import { CoreGreetTransformer } from "./transformers";
import { Greeter } from "./greeter";
import { GREETER_TRANSFORMERS } from "./greeter";

// "Barrel" exports.
// --
// NOTE: Traditionally, this kind of exporting of the "public" values from a module is
// done in a "barrel" file (ie, index.ts). However, in order to keep this demo smaller,
// I'm co-opting the Module file to play double-duty as both the module and the "barrel".
export { Greeter } from "./greeter";
export { GREETER_TRANSFORMERS } from "./greeter";
export { IGreetTransformer } from "./transformers";

@NgModule({
	providers: [
		Greeter,

		// When Angular instantiates the Greeter class, it's going to inject this
		// collection of Transformers. By default, the Greeter module is configured to
		// supply the one "core" Transformer. However, the application at large can
		// easily add to this "multi" dependency collection.
		{
			provide: GREETER_TRANSFORMERS,
			multi: true,
			useClass: CoreGreetTransformer
		}
	]
})
export class GreeterModule {
	// ... nothing to do here.
}

In the root module, however, that's where our configuration approach changes. In the root module, we're going to add two greet transformers, one using dependency-injection that will be provided to the greeter during instantiation; and, one that we'll add to the Greeter in the "run block" after it has been instantiated:

// 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 { ComplimentTransformer } from "./app-transformers";
import { Greeter } from "./greeter/greeter.module";
import { GreeterModule } from "./greeter/greeter.module";
import { GREETER_TRANSFORMERS } from "./greeter/greeter.module";
import { YellingTransformer } from "./app-transformers";

@NgModule({
	bootstrap: [ AppComponent ],
	imports: [ BrowserModule, GreeterModule ],
	providers: [
		// As part of the Greeter "configuration", we can setup a collection of
		// Transformers to be injected into the Greeter as part of the instantiation
		// process. In this way, we are replacing the Angular 1 "configuration phase"
		// with dependency-injection mechanics.
		{
			provide: GREETER_TRANSFORMERS,
			multi: true, // <-- This creates an array for a single injectable.
			useClass: ComplimentTransformer
		},


		// However, configuration is NOT AN EITHER-OR CONCEPT. We can configure a
		// service using dependency-injection (above); but, we can also, at the very
		// same time, use a run-block to call methods on instantiated services as the
		// last step prior to running the application.

		// This is the transformer that we want to add to our Greeter service. In order
		// to make it available in the "run block", we have to "provide" it so that
		// Angular can instantiate it as a dependency.
		YellingTransformer,

		// Now, let's create a "run block" that will inject the above YellingTransformer
		// into the already-instantiated Greeter service.
		{
			provide: APP_INITIALIZER,
			multi: true,
			deps: [ Greeter, YellingTransformer ],
			useFactory: function( greeter: Greeter, yellingTransformer: YellingTransformer ) : () => void {

				return( runblock );

				function runblock() : void {

					greeter.addTransformer( yellingTransformer );

				};

			}
		}
	],
	declarations: [ AppComponent ]
})
export class AppModule {
	// ... nothing to do here.
}

As you can see, in addition to building up the multi:true DI token, GREETER_TRANSFORMERS, we're also building up the multi:true DI token, APP_INITIALIZER. Each APP_INITIALIZER is a function that runs at the end of the bootstrapping process, right before the application is "started." In our case, we're using a factory function to provide the APP_INITIALIZER callback so that we can have the Angular Injector provide references to the instantiated Greeter service and the YellingTransformer that we want to add.

At this point, when the application boots-up, we'll have provided 3 transformers to the Greeter service - 2 with dependency-injection and 1 with a run block interaction. Now, when we run the root component:

// Import the core angular services.
import { Component } from "@angular/core";

// Import the application components and services.
import { Greeter } from "./greeter/greeter.module";


@Component({
	selector: "my-app",
	template:
	`
		<em>Look in the console to see the Greeter result.</em>
	`
})
export class AppComponent {

	// I initialize the component.
	constructor( greeter: Greeter ) {

		console.group( "Testing Greeter" );
		console.log( greeter.greet( "Tricia" ) );
		console.groupEnd();

	}

}

... we get the following page output:

Configuring Angular 2 services with both dependency-injection and run blocks.

As you can see, all three greet transformers have been used to generate the greeting. Two of them from dependency-injection, one of them from the run block.

NOTE: I'm not showing the rest of the code since it's all the same as in the previous post.

While configuration in Angular 2 is quite a bit different than the configuration in Angular 1, all the same outcomes are possible. Collections can be provided to services using multi:true dependency-injection tokens. And, anything that can't be provided as an injectable can be provided as part of a run block at the end of the bootstrapping process.

Want to use code from this post? Check out the license.

Reader Comments

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel