27

Using typescript, I can easily bind classes to themselves:

bootstrap(MyAppComponent, [MyClass]);

However, I would like to bind my class to an interface, like such:

boostrap(MyAppComponent, [???]);

such that I can inject it as follows:

class MyAppComponent {
    constructor(my_class : IMyClass){
    }
};

Is this possible in Angular2? If yes, how to I have to specify the binding?

Wai Ha Lee
  • 8,598
  • 83
  • 57
  • 92
Wilbert
  • 7,251
  • 6
  • 51
  • 91

2 Answers2

26

To make it short the problem is that Interfaces disappear when typescript is compiled. So you'd have to use @Inject with a string.

Or there's another option, if you check the last article of Victor Savkin you can find this in the comments :

Some background. In TypeScript, interfaces are structural and are not retained at runtime. So you have to use ILoginService as follows:

constructor(@Inject("ILoginService") s:ILoginService).

You don't have to use a string - any object can be passed in there. We actually provide an object called OpaqueToken that can be used for this purpose.

interface ILoginService { login(credentials);}
const ILoginService = new OpaqueToken("LoginService");

can be used like this:

constructor(@Inject(ILoginService) s:ILoginService).
LazarusG
  • 150
  • 1
  • 5
  • 16
Arnaud Boeglin
  • 763
  • 6
  • 12
  • why is `new OpaqueToken("LoginService")` getting passed `LoginService` and not `ILoginService`? – Danny Bullis Oct 28 '16 at 23:50
  • @DannyBullis its not passed LoginService for usage, what is done here is that we create a OpaqueToken and pass it a value that we want to use later when we request the OpaqueToken in the constructor. This here is just an example string. Note that OpaqueToken is deprecated and InjectionToken should be used. More for understanding can be found here https://stackoverflow.com/questions/41289264/what-is-in-angular-2-opaque-token-and-whats-the-point – hogan Aug 06 '19 at 06:30
8

I dont know if it is possible with interface as interface will not be available at runtime (javascript does not know about interface). But it can be done using abstract classes.

//abstract-parent-service.ts

export class DatabaseService{
    getService: ()=>string;
}

//hibernate.service.ts

import {DatabaseService} from "./abstract-parent-service";

export class HibernateService implements DatabaseService{
  constructor() { }
  getService() {
    return "i am hibernate";
  }
}

//jdbc.service.ts

import {DatabaseService} from "./abstract-parent-service";

export class JDBCService implements DatabaseService{
  constructor() { }
  getService() {
    return "i am Jdbc";
  }
}

//cmp-a.component.ts

import {DatabaseService} from "./abstract-parent-service";
import {HibernateService} from "./hibernate.service";

@Component({
    selector: 'cmp-a',
    template: `<h1>Hello Hibernate</h1>`,
    providers: [{provide: DatabaseService, useClass: HibernateService}]
})
export class CmpAComponent {
    constructor (private databaseService: DatabaseService) {
        console.log("Database implementation in CompA :"+this.databaseService.getService());
    }
}

//cmp-b.component.ts

import {DatabaseService} from "./abstract-parent-service";
import {HibernateService} from "./hibernate.service";

@Component({
    selector: 'cmp-b',
    template: `<h1>Hello Jdbc</h1>`,
    providers: [{provide: DatabaseService, useClass: JDBCService}]
})
export class CmpAComponent {
    constructor (private databaseService: DatabaseService) {
        console.log("Database implementation in CompA :"+this.databaseService.getService());
    }
}

But the problem with this implementation is HibernateService and JDBCService are not able to extend any other class now because they have already got married with DatabaseService.

class A{
    constructor(){
        console.log("in A");
    }
}
class B extends A{
    constructor(){
        super();
        console.log("in B");
    }
}
class C extends A{
    constructor(){
        super();
        console.log("in C");
    }
}
let c = new C();

//This thing is not possible in typescript
class D extends B, C{//error:  Classes can only extend a single class
    constructor(){
        super();// which constructor B or C
        console.log("in D");
    }
}

If you are using this pattern for DI, make it sure that your child class services are not going to extend any other functionality in future.

Arun Kumar
  • 323
  • 1
  • 4
  • 15