Using Abstract Classes As Dependency-Injection Tokens For Swappable Behaviors In Angular 4.2.3
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:
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.
Want to use code from this post? Check out the license.
Reader Comments
nice implementation bro.
Educational investigation, and *very* clear writeup - thanks!
@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).
Implementing an abstract class ... tell it to a Java/C# guy and it will blew his mind
@Ori,
Ha ha ha, I don't want to do anything dangerous :P
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?
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???
@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.
@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.
Yo, thanks for the article. This is powerful stuff for abstraction. Nice to see i was doing it correctly
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
@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 :)
Thanks a lot for sharing! This article definitely cleaned up some things!
So I guess, for such use case, it wouldn't be wise to use a service for the thing you wanted to achieve.
My takeaway from this is that I should use an abstract class or an injection token whenever I have to deal with small encapsulated objects that are not directly available at runtime.
Please correct me if I'm wrong!
Thank you!
@Andrei,
So, the nice thing about the Abstract Class is that you can extend it without having to worry about the private variables. However, this doesn't prevent you from being able to use a Service as the DI token. But, if you do use a Service for the DI token, I would recommend using
implements
instead ofextends
and then providing an interface rather than the Service reference.For example, imagine you have some sort of Error Logging service that you wanted to be swappable. You could use
ErrorLogger
as the DI token; but, I would have it implement some interface likeHandleError
:This way, if you want to override the
ErrorLogger
with something likeNewRelicErrorLogger
, you can have it implementHandleError
instead of trying to extendErrorLogger
:Then, in your dependency-injection configuration, you can override the Service the way you would with anything else:
... and, in your D/I, you could still use the base-Service as the "Type":
... where the DI token is the Service
ErrorLogger
, but it's actually receivingNewRelicErrorLogger
as the runtime instance.Does that make sense? So, there are no hard-and-fast rules. The trick is really that you should avoid trying to
extends
a Service. You should either useextends
with an Abstract Class; or, you shouldimplements
with an Interface. Just don't useimplements
with a concrete Service.Thank you for sharing this approach!
This is a tangential question to this design.
I have a code generated module in a Library that requires a
SignalRService
.Are you aware of any approach that lets me define the module such that at design time, when its imported by a consumer module, there is knowledge that
SignalRService
needs to be provided as well?I don't think there is but thought I would put this out there. My fall back is to check 'SignalRService` in the ctor() of the Lib module at run time...