In the majority of cases, when you inject something into an Angular 2 Directive or Service, you're injecting an instance of some Class. The other day, however, when I was exploring at Auth0 Rules and Client IDs , I needed to inject a Class definition itself, not an instance of said Class. This way, my demo component could handle the instantiation of the Class rather than have Angular 2's dependency-injection container handle it. At the time, I was stumped on how to do this in a way that made TypeScript happy. But, I have since figured out a clean and flexible way to get TypeScript and Angular 2 to inject "newable" values into my components and services.

Run this demo in my javascript Demos project on GitHub .

When you hear about having to inject a "newable" value (ie, a Class that can be instantiated) into another Class or Service in Angular 2, your first reaction might be to question the very need to do this. After all, if you need to instantiate a Class, why not just "import" the Class and instantiate it? There's nothing fundamentally wrong with that. But, it does tightly couple the two classes together. Which may be fine. But, the nice thing about having a dependency-injection container in Angular 2 is that we can invert the dependencies, relating the two values by Interface, not by implementation.

Now, when using dependency-injection in TypeScript, our constructor parameters need to have a type annotation. For example:

constructor( myService: Service ) { ... }

Here, we're injecting an instance of the Class Service. But, if we want to inject the Service Class itself - not an instance of Service - what Type annotation do we give it? It's not of "Type Service" because it's not an instance. Instead, it needs to be a Type that can be "newed" (ie, instantiated) to create an instance of Type Service.

In TypeScript, you can define a "newable" interface as an interface that has a "new()" method that returns the desired Type:

export interface INewableService { new(): Service;

We can use this syntax to define our newable Interface; but, in Angular 2's dependency-injection system, we can't actually use Interfaces as dependency-injection tokens since Interfaces don't compile down to actual JavaScript values. We could use the @Inject() annotation and an OpaqueToken() to inject the Service Class; but, I try to avoid @Inject() when possible because it introduces more moving parts.

By using some TypeScript magic, we can actually create a "Type" that acts as both the newable interface and the dependency-injection token. It turns out, in TypeScript, if you give an Interface and a Class that implements that interface the same name, you can define methods on the interface without having to implement them in the class:

export interface NewableService { new(): Service; export class NewableService implements NewableService { // ... I don't need to implement new() from interface.

We can now use this Class as a dependency-injection token. And, since it implements the "newable" interface, it means we can also use it for the type annotation to make TypeScript happy.

To explore this in an Angular 2 setting, let's create a GreeterService that requires a name and can generate a greeting. For this demo, we don't want to create this service as a single instance; instead, we want our root component to be able to create multiple instances of it. To do this, we're going to export a "newable" Type for the GreeterService that allows the GreeterService Class to be dependency-injected:

// Most of the time, when we want Angular to dependency-inject a Class, we want it to // inject an INSTANCE of that Class. But, sometimes, we just want to inject the Class // itself so that the recipient of the Class can take care of instantiation (perhaps // creating multiple instances from the Class). In that case, the thing we're injecting // isn't of "type Class", it's of "type newable". Now, to get Angular and TypeScript to // work together to inject a "newable" value, we need two different constructs: // -- // * A dependency-injection token. // * A newable Interface that returns the correct Type. // -- // In TypeScript, we can sort of merge those two concepts by creating a Class that // implements an Interface of same name (as the Class). When a Class and an Interface // have the same name, it allows you to define methods on the Interface without having // to implement them in the Class. By using this feature, we can create a Type // (ie, Class) that will act as both the dependency-injection token and as the type // annotation that defines the newable behavior. // -- // Read more: // The Interface defines the newable behavior (which returns GreeterService). export interface NewableGreeterService { new( name: string ): GreeterService; // The Class defines the Type (which also acts as our dependency-injection token). export class NewableGreeterService implements NewableGreeterService { // ... // ----------------------------------------------------------------------------------- // // ----------------------------------------------------------------------------------- // export class GreeterService { protected name: string; // I initialize the Greeter service. constructor( name: string ) { = name; // --- // PUBLIC METHODS. // --- // I return a greeting message. public getGreeting() : string { return( `Hello, ${ }.` );

Here, you can see that we're using the same pattern as above - we've created both an Interface and a Class that share the same name, NewableGreeterService. This allows us to define the "new()" method on the interface without having to implement on the Class. This allows us to use the NewableGreeterService Type as both the dependency-injection token for the GreeterService Class - no instance - while also providing the interface for the type annotation.

Now, to get this to work, we have to tell our Angular 2 dependency-injection container not to try an instantiate this NewableGreeterService Class when being cachced. Instead, we want Angular 2 to use the "value" NewableGreeterService, which can outline in our App Module's "providers" collection:

// Import the core angular services. import { BrowserModule } from "@angular/platform-browser"; import { NgModule } from "@angular/core"; // Import the application components and services. import { AppComponent } from "./app.component"; import { GreeterService } from "./greeter.service"; import { NewableGreeterService } from "./greeter.service"; // ----------------------------------------------------------------------------------- // // ----------------------------------------------------------------------------------- // // I provide an altern

本文前端(javascript)相关术语:javascript是什么意思 javascript下载 javascript权威指南 javascript基础教程 javascript 正则表达式 javascript设计模式 javascript高级程序设计 精通javascript javascript教程

主题: JavaJavaScriptGitGitHubUBETH
tags: Class,dependency,newable,injection,interface
本文标题:Injecting "Newable" Classes Using TypeScript And Dependency-Injection In Ang ...

技术大类 技术大类 | 前端(javascript) | 评论(0) | 阅读(154)