Check out our Angular Book Series.

Create Two Services from One Class - Dependency Injection and Angular - Part 5

I was chatting with a colleague recently, and he had some confusion about how dependency injection works with Angular. There are a bunch of ways to set up Dependency Injection with Angular, but as always the documentation seems lacking. I'm writing this series of posts to discuss the ways and to explain what works and what won't work.

Check out the previous entries:

  1. The one where we set up a project and create a default Angular Service
  2. The one where we remove the providedIn configuration object
  3. The one where we remove all the injectable metadata
  4. Component Specific Providers

Create Two Services from One Class

I had an issue come up during some recent development. Two sections of an application needed to store settings, each using an identical class structure. However, the two settings could not be mixed, so I could use the same service for each. I wanted to create two separate services, using the same class. Since our main navigation would need to access these respective services, I could just inject these values at a component level. The answer was to create my own injection token, and inject the components slightly differently. I'll explain that here.

First, let's create the class. I didn't use the Angular CLI for this one. I just created a class named service5.service.ts:


export class Service5Service {
myValue = 'default 5';
constructor() { }
}

This follows a similar approach to services we created earlier.

Now open up the app.module.ts:


export const SERVICE5_VIEW1 = new InjectionToken<Service5Service>('Service5View1');
export const SERVICE5_VIEW2 = new InjectionToken<Service5Service>('Service5View2');

This uses the Angular InjectionToken class to create two separate instances of an injectable Service5Service. Be sure to import the InjectionToken:


import {InjectionToken, NgModule} from '@angular/core';

And the Service5Service:


import {Service5Service} from './services/service5.service';

Next, we need to tell the @NgModule to use these InjectionTokens to create a new service. We do that as part of the providers array:


providers: [
Service2Service,
Service3Service,
{ provide: SERVICE5_VIEW1, useValue: new Service5Service()},
{ provide: SERVICE5_VIEW2, useValue: new Service5Service()}
],

Instead of listing a specific class in the providers array, we are providing a configuration object. The first value, provider, tells us about the service, and in this case we are specifying the InjectorToken we shared as a constant. The second property, useValue, tells us the value to use when this InjectionToken is referenced. In this case, we created a new instance of the Service5Service.

The injection syntax into a component is a bit different. Let's inject it as part of the constructor of view1.component.ts:


@Inject(forwardRef(() =>
SERVICE5_VIEW1)) @Optional() public service5: Service5Service

Be sure to import the inject and forwardRef metadata:


import {Component, forwardRef, Inject, OnInit, Optional} from '@angular/core';

And Service5Service:


import {Service5Service} from '../services/service5.service';

And the injector constant:


import {SERVICE5_VIEW1} from '../app.module';

What does this code do? The Inject metadata tells Angular not to perform a lookup for a specific class, but rather to look for this specific injector in order to get the appropriate value. The forwardRef function tells Angular to populate this value after it is defined. In certain cases, this value may not be set up before the view is initialized, so this makes sure Angular will set it up after the fact.

Once this is injected, we use it just like we would use any other class. Open up view1.component.html:


<h1>View 1: Service 5</h1>
<input [(ngModel)]="service5.myValue">

As with some of our past examples, we added an input that will change myValue inside service5.

Now open up view2.component.ts. We want to follow the same path, but with the other injector token. Add the injection to the constructor:


@Inject(forwardRef(() =>
SERVICE5_VIEW2)) public service5: Service5Service

This follows the same approach from the previous sample. Be sure to import the Inject metadata:


import {Component, Inject, OnInit} from '@angular/core';

The SERVICES5_VIEW2 constant:


import {SERVICE5_VIEW2} from '../app.module';

And the Services5Serivce class:


import {Service5Service} from '../services/service5.service';

The code for injecting SERVICES5_VIEW2 into view2 works identically as the code for injecting SERVICES5_VIEW1 into view 1.

Now open view2.component.html, and the output display from Service 5:


<h1>View 2: Service 5</h1>
{{service5.myValue}}

If we've set this up right View 1 service5 and View 2 service5 instances are completely independent, and changing one will not change the other.

Run the app and test:

You'll see that edits in view1 have no affect on edits in view2, because we have completely different services even if they use the same backend class.

What Next?

You can play with the code here.

This is the end of my exploration into Angular Dependency Injection. You should have a good understanding of how to create a service, how to inject--either using the providedIn configuration object or the providers array--and how to make the service specific to a single component.

Comments (Comment Moderation is enabled. Your comment will not appear until approved.)
Comments are not allowed for this entry.
All Content Copyright 2005, 2006, 2007, 2008, 2009 Jeffry Houser. May not be reused without permission
BlogCFC was created by Raymond Camden. This blog is running version 5.9.2.002.