Ionic2: how to configure providers with alternate implementations of a TypeScript interface


#1

ionic CLI 2.0.0-beta.25
angular2 2.0.0-beta.15
ionic-angular 2.0.0-beta.6

My database service is defined as a TypeScript interface with a couple implementations. I want to use a mock data implementation for development, and configure providers to use another production implementation when it’s ready.

db.ts

export interface MyDb {
}

mock-db.ts

@Injectable()
export class MockMyDb implements MyDb {
}

app.ts

@App({
templateUrl: ‘build/app.html’,
// http://ionicframework.com/docs/v2/api/config/Config/
config: {},
providers: [
provide(MyDb, {useClass: MockMyDb})
]
})
class MyApp {
rootPage: any = HomePage;
pageComponentFor: { [name: string]: any};

constructor(private app: IonicApp, private platform: Platform, public db: MyDb) {

This doesn’t work:
Uncaught ReferenceError: MyDb is not defined

Docs said this could be a string, but this results in an error too:

provide(‘MyDb’, {useClass: MockMyDb})

EXCEPTION: Cannot resolve all parameters for ‘MyApp’(IonicApp, Platform, Events, ?). Make sure that all the parameters are decorated with Inject or have valid type annotations and that ‘MyApp’ is decorated with Injectable.

Can I not use a TypeScript interface in this way? How do I retain the interface as an object in compiled JavaScript so that providers can use it?
Is there a trick to make the interface object retained in the compiled JavaScript code? declare var?
Use a class and extend instead of interface and implements?
What is the correct way to do this in TypeScript?

Also, it wasn’t until I found this blog that I realized you could put provide() calls within Ionic2’s App.providers[], so maybe that should be noted in the FAQ.


#2

The fundamental problem is that interfaces don’t exist in transpiled TypeScript. I think you have a couple of options.

Make MyDb a class

export class MyDb {
}
export class MockMyDb extends MyDb {
}

No changes to App.

Inject as a string

You got halfway to this one, and MyDb and MockDb can stay the same, you make the provide key 'MyDb' instead of MyDb, but then in order to convey that to the DI system, you need to annotate the App constructor parameter like so:

@Inject('MyDb') public db:MyDb

#3

I found the solution: http://stackoverflow.com/questions/32254952/binding-a-class-to-an-interface

So, use a string:

provide(‘MyDb’, {useClass: MockMyDb})

and in app.ts

constructor(private app: IonicApp, private platform: Platform, @Inject(‘MyDb’) public db: MyDb) {

Though it would still be nice if there was a trick to avoid needing @Inject() everywhere.


#4

Ok, thanks.

There are abstract classes and methods as of TypeScript 1.6
https://www.typescriptlang.org/docs/handbook/classes.html

So maybe that’s the way to go to avoid @Inject()


#5

since ionic 2.0.0-rc3 @Inject it is not necessary:

providers: [
    { provide: ErrorHandler, useClass: IonicErrorHandler },
....
    { provide: DepositoryProvider, useClass: MockDepositoryProvider}
  ]

#6

Things have changed a bit since this thread was created. New readers might be interested in this.