Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at Scotch On The Rock (SOTR) 2010 (London) with: Claude Englebert and Ciqala Burt and Mark Drew
Ben Nadel at Scotch On The Rock (SOTR) 2010 (London) with: Claude Englebert@cfemea ) , Ciqala Burt@ciqala ) , and Mark Drew@markdrew )

Using Abstract Classes As Dependency-Injection Tokens For Swappable Behaviors In Angular 4.2.3

By Ben Nadel on

Over the weekend, I was looking at ways to cleanly encapsulate Firebase's realtime API behind a stream-based data access layer (DAL). As part of that exploration, I had to provide multiple gateway implementations (or behaviors) that could be swapped in and out seamlessly. At first, I wasn't quite sure how to accomplish this in Angular 4. After some trial and error, though, I discovered that I could use an Abstract class as the dependency-injection token for my swappable services. This felt like an elegant approach for interchangeable services.

My first instinct was to use a TypeScript Interface to define the behavior of the swappable classes. Unfortunately, you can't use an Interface as a dependency-injection token in Angular 4 since an Interface doesn't actually have a runtime artifact (it's only used during compile-time to drive type-safety). As such, my first attempt used an InjectionToken do define constructor-argument meta-data:

  • // Import the core angular services.
  • import { Component } from "@angular/core";
  • import { Inject } from "@angular/core";
  • import { InjectionToken } from "@angular/core";
  •  
  • // ----------------------------------------------------------------------------------- //
  • // ----------------------------------------------------------------------------------- //
  •  
  • // This injection token can be used to configure the Providers as well as the @Inject()
  • // meta-data that will tell the dependency-injection container how to locate the desired
  • // class instance.
  • var GreeterToken = new InjectionToken<GreeterInterface>( "Greeter behavior" );
  •  
  • interface GreeterInterface {
  • greeting() : string;
  • }
  •  
  • class NiceGreeter implements GreeterInterface {
  •  
  • public greeting() : string {
  •  
  • return( "Hello, what a pleasure to meet you." );
  •  
  • }
  •  
  • }
  •  
  • class MeanGreeter implements GreeterInterface {
  •  
  • public greeting() : string {
  •  
  • return( "Hello, you are a doofus!" );
  •  
  • }
  •  
  • }
  •  
  • // ----------------------------------------------------------------------------------- //
  • // ----------------------------------------------------------------------------------- //
  •  
  • @Component({
  • selector: "my-app",
  • providers: [
  • // For this application, let's provide the MeanGreeter instance when the
  • // GreeterToken needs to be injected into the App component.
  • {
  • provide: GreeterToken,
  • useClass: MeanGreeter // <--- Defining the swappable implementation.
  • }
  • ],
  • styleUrls: [ "./app.component.css" ],
  • template:
  • `
  • <p>
  • <strong>Greeting:</strong> {{ greeting }}
  • </p>
  • `
  • })
  • export class AppComponent {
  •  
  • public greeting: string;
  •  
  • // I initialize the app component.
  • constructor( @Inject( GreeterToken ) greeter: GreeterInterface ) {
  •  
  • this.greeting = greeter.greeting();
  •  
  • }
  •  
  • }

As you can see, in the AppComponent constructor(), I have to use the InjectionToken to define an @Inject() decorator. This tells the Angular dependency-injection container what I'm actually trying to inject for the given "greeter" argument. And, when we run this version of the app, we get the following output:


 
 
 

 
 Using abstract classes as dependency-injection (DI) tokens in Angular 4. 
 
 
 

As you can see, this approach works. But, I don't like it. There's something that feels janky about having to use the @Inject() decorator in order to inject a Class instance. After all, the whole dependency-injection system revolves around Class instances.

My next thought was to try to have one class "implement" the other class. This way, I could use the "base" class as the dependency-injection token:

  • // Import the core angular services.
  • import { Component } from "@angular/core";
  •  
  • // ----------------------------------------------------------------------------------- //
  • // ----------------------------------------------------------------------------------- //
  •  
  • interface GreeterInterface {
  • greeting() : string;
  • }
  •  
  • class NiceGreeter implements GreeterInterface {
  •  
  • public greeting() : string {
  •  
  • return( "Hello, what a pleasure to meet you." );
  •  
  • }
  •  
  • }
  •  
  • // CAUTION: Here, I am saying that the "MeanGreeter" IMPLEMENTS the "NiceGreeter."
  • class MeanGreeter implements NiceGreeter {
  •  
  • public greeting() : string {
  •  
  • return( "Hello, you are a doofus!" );
  •  
  • }
  •  
  • }
  •  
  • // ----------------------------------------------------------------------------------- //
  • // ----------------------------------------------------------------------------------- //
  •  
  • @Component({
  • selector: "my-app",
  • providers: [
  • // For this application, let's provide the MeanGreeter instance when the
  • // NiceGreeter needs to be injected into the App component.
  • {
  • provide: NiceGreeter,
  • useClass: MeanGreeter // <--- Defining the swappable implementation.
  • }
  • ],
  • styleUrls: [ "./app.component.css" ],
  • template:
  • `
  • <p>
  • <strong>Greeting:</strong> {{ greeting }}
  • </p>
  • `
  • })
  • export class AppComponent {
  •  
  • public greeting: string;
  •  
  • // I initialize the app component.
  • constructor( greeter: NiceGreeter ) {
  •  
  • this.greeting = greeter.greeting();
  •  
  • }
  •  
  • }

As you can see, in this approach, the MeanGreeter class is "implementing" the NiceGreeter class. This is simpler than the InjectionToken approach. And, it works at first. But, I don't like it. There's something that seems janky about asking for a NiceGreeter type and receiving a MeanGreeter instance. The semantics of it don't sit right.

And, it really doesn't matter that it feels janky because it turns out to be a very brittle approach. Having one class "implement" another class breaks the moment you add a private property to the base class. Imagine that I wanted to refactor the NiceGreeter class such that it stored the greeting value as a private property:

  • class NiceGreeter implements GreeterInterface {
  •  
  • private value: string = "Hello, what a pleasure to meet you.";
  •  
  • public greeting() : string {
  •  
  • return( this.value );
  •  
  • }
  •  
  • }
  •  
  • // CAUTION: Here, I am saying that the "MeanGreeter" IMPLEMENTS the "NiceGreeter."
  • class MeanGreeter implements NiceGreeter {
  •  
  • public greeting() : string {
  •  
  • return( "Hello, you are a doofus!" );
  •  
  • }
  •  
  • }

At this point, the TypeScript compiler starts to complain:

Error TS2420: Class 'MeanGreeter' incorrectly implements interface 'NiceGreeter'.
Property 'value' is missing in type 'MeanGreeter'.

In TypeScript, when one class implements another class, it has to implement all of its members, not just the public methods. That includes the private properties and the private methods. Which is, of course, antithetical to the very meaning of "private." As such, this approach becomes a non-starter.

The approach that I finally settled on was using an Abstract class to define both the interface and the dependency-injection token for the swappable behaviors:

  • // Import the core angular services.
  • import { Component } from "@angular/core";
  •  
  • // ----------------------------------------------------------------------------------- //
  • // ----------------------------------------------------------------------------------- //
  •  
  • // Because an Abstract class has a runtime representation, we can use it as a
  • // dependency-injection (DI) token in Angular's DI container. And, since each concrete
  • // class has to implement or extend this abstract base class, it means that the base
  • // class can act as the "interface" to the behavior as well.
  • abstract class Greeter {
  • abstract greeting() : string;
  • }
  •  
  • // NOTE: We could have also used "extends Greeter" if Greeter provided base
  • // functionality that needed to be shared with its concrete classes.
  • class NiceGreeter implements Greeter {
  •  
  • public greeting() : string {
  •  
  • return( "Hello, what a pleasure to meet you." );
  •  
  • }
  •  
  • }
  •  
  • // NOTE: We could have also used "extends Greeter" if Greeter provided base
  • // functionality that needed to be shared with its concrete classes.
  • class MeanGreeter implements Greeter {
  •  
  • public greeting() : string {
  •  
  • return( "Hello, you are a doofus!" );
  •  
  • }
  •  
  • }
  •  
  • // ----------------------------------------------------------------------------------- //
  • // ----------------------------------------------------------------------------------- //
  •  
  • @Component({
  • selector: "my-app",
  • providers: [
  • // For this application, let's provide the MeanGreeter instance when the
  • // Greeter needs to be injected into the App component.
  • {
  • provide: Greeter,
  • useClass: MeanGreeter // <--- Defining the swappable implementation.
  • }
  • ],
  • styleUrls: [ "./app.component.css" ],
  • template:
  • `
  • <p>
  • <strong>Greeting:</strong> {{ greeting }}
  • </p>
  • `
  • })
  • export class AppComponent {
  •  
  • public greeting: string;
  •  
  • // I initialize the app component.
  • constructor( greeter: Greeter ) {
  •  
  • this.greeting = greeter.greeting();
  •  
  • }
  •  
  • }

As you can see, this approach treats the Abstract Class much like an Interface that both the NiceGreeter and the MeanGreeter behaviors have to adhere to. The semantics of it also feel good. My App component asks for a "Greeter"; so, it doesn't feel janky to provide either of the sub-classes (if you will) as the runtime implementations.

In this particular case, I'm using the "implements" keyword since I don't actually want to inherit any behaviors from the base class; but, you could use the "extends" keywords if you wanted the Abstract Class to act as the inherited super class for the individual behaviors.

Now, to be clear, I'm not advocating that you start using abstract classes for all of your dependency-injection needs. I only came upon this approach because I needed to be able to swap behaviors within my own Angular 4 application. For this particular use-case, the abstract class seemed like an elegant solution.



Looking For A New Job?

100% of job board revenue is donated to Kiva. Loans that change livesFind out more »

Reader Comments

@Dawesi, @Wayne,

Thank you very much. And, actually, if you look at the way Angular implements the "Location Strategy" so that it can be URL-based or Hash-based, they use this approach (which is, in part, where I got the idea from).

Reply to this Comment

Always learned a lot from your post.

I have a problem to see whether you have a solution.

// Module 1
...
providers: [
ServiceA,
ServiceB
]
...

abstract class BaseService {}
class ServiceA extends BaseService {}
class ServiceB extends BaseService {}

// Module 2 - depends on Module 1
---
providers: [
BaseService // This is wrong because the abstract class!
// But I don't know which concrete
// service to use at the time.
]
---
class C {
constructor(private baseService: BaseService) {}
}

// Module 3 - depends on Module 1 and Module 2
...
providers: [
{provide: BaseService, useClass ServiceA}
]
...
class D {
constructor(private baseService: BaseService) {}
}

The question is how to dynamically inject the service into the middle module?

Reply to this Comment

Nice post about how to use an abstract class with a aservice in angular!!

I have a similar question and don't know how to solve this with my abstract class:

providers: [

provide: Abstract_Class_SERVICE,
useClass: TestService1,

provide: Abstract_Class_SERVICE,
useClass: TestService2,

provide: Abstract_Class_SERVICE,
useClass: TestService3,

....
]

Angular is always just running the last TestService???

Reply to this Comment

@Rick,

The whole Module system in Angular is still a bit confusing for me. But, the way I understand it, the middle module shouldn't have to "provide" the service. The root module will provide the service implementations; then, the classes in the middle module just have to ask for the right one based on the DI token.

One thing that I've come to embrace whole-heatedly is that I DO NOT WANT my code to do anything surprising. This comes to class implementations as well. If the whole app uses the same implementation, then I can just use said class (or the abstract class) as the DI token. But, if one part of the app needs "ImplementationOne" and another part of the app needs "ImplementationTwo", then each consuming context should just ask for the given implementation and not worry too much about the abstraction level.

So, in your example, Class C and Class D should explicitly ask for either "Service A" or "Service B". Then, there is no confusion.

Now, if you *really* need your classes to use the base-class as the DI token, AND have that token mean different things to different classes, then you probably have to use a Factory in your Module providers list. So, instead of just saying "use this class for that DI token", you probably have to construct the class yourself:

{
. . . provide: ClassC,
. . . useFactory: function( serviceA ) {
. . . . . . return( new ClassC( serviceA ) );
. . . },
. . . deps: [ ServiceA ]
}

Of course, then you don't get the benefit of all the DI functionality at the class level, since you are doing it manually (though the "deps" will still handle all the instantiation).

It's a tricky problem, to be sure. Hopefully, I have understood what you are asking.

Reply to this Comment

@Steffen,

That is correct. Essentially, you are overwriting the dependency-injection (DI) token twice. Then, when the Angular app is being bootstrapped, it will use whatever the last definition was, which is TestSevice3 in your case.

Reply to this Comment

Yo, thanks for the article. This is powerful stuff for abstraction. Nice to see i was doing it correctly

Reply to this Comment

Great article. Using 'abstract' certainly seems like the logical approach, especially as it can be used directly for Injection, as opposed to using a 'class' and 'interface' combined.

The other advantage of using 'abstract' class over interface, is that if you add concrete methods to the 'abstract', they are inherited if extended, but still act as interface methods if the abstract is implemented

Reply to this Comment

@Brian,

Absolutely! That's a great feature of extending another class.

The one thing that I struggle with, in that case, is when the base-class requires constructor arguments that need to be injected into the sub-class. Something about that feels a little funny (meaning if the base-class doesn't use the constructor arguments -- but only needs them to pass to super constructor). Not sure if that is a "bad thing" or an indication that I'm using the base class incorrectly? Or, maybe just how it works sometimes.

Though, now that I am saying that out loud, if I need a constructor argument simply for passing it to a super constructor, I am thinking that this is an indication that the resultant behavior can perhaps better be modeled in an external class somehow.

Anyway, just stream of consciousness :)

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.