Reputation: 2023
I'm currently trying to clean up some code in order to program against interfaces rather than against implementations but can't figure out how to.
To be more specific, I'm using: * TypeScript 1.5.0 beta -> transpiled to ES5 / commonjs * SystemJS to load the modules
I currently try to use external modules as follows:
posts.service.ts file:
///<reference path="../../../typings/tsd.d.ts" />
///<reference path="../../../typings/typescriptApp.d.ts" />
...
export interface PostsService{ // 1
fetchPosts(): Rx.Observable<any>;
}
export var PostsService:PostsServiceImpl; // 2
...
export class PostsServiceImpl implements PostsService { // 3
...
constructor(){
console.log('Loading the Posts service');
}
...
fetchPosts(): Rx.Observable<any>{
...
}
and that module is imported in posts.ts:
///<reference path="../../../typings/tsd.d.ts" />
///<reference path="../../../typings/typescriptApp.d.ts" />
import {PostsService, PostsServiceImpl} from 'components/posts/posts.service';
@Component({
selector: 'posts',
viewInjector: [
//PostsServiceImpl
//PostsService
bind(PostsService).toClass(PostsServiceImpl)
]
})
...
export class Posts {
private postsServices: PostsService;
constructor(postsService: PostsService) {
console.log('Loading the Posts component');
this.postsServices = postsService;
...
}
...
}
The above code compiles correctly, but basically my issue boils down to the fact that Angular won't inject an instance of PostsServiceImpl in the Posts component.
Of course it's simply because I don't find the correct way to declare/export/import everything
Without export interface PostsService ...
, I get TS compilation errors because I use it in the posts controller.
Without export var PostsService:PostsServiceImpl;
I get TS compilation errors in the @View decorator's viewInjector
In the generated js code I only find exports.PostsService;
which is added because of the export var... I know that interfaces disappear at runtime.
So I'm a bit lost in all this. Any practical ideas how I can use interface based programming w/ TypeScript & Angular 2?
Upvotes: 8
Views: 25758
Reputation: 1162
In typescript you can implement classes.
Example
config.service.ts:
export class ConfigService {
HUB_URL: string;
}
export class ConfigServiceImpl implements ConfigService {
public HUB_URL = 'hub.domain.com';
}
export class ConfigServiceImpl2 implements ConfigService {
public HUB_URL = 'hub2.domain.com';
}
app.component.ts:
import { Component } from '@angular/core';
import { ConfigService, ConfigServiceImpl, ConfigServiceImpl2 } from './config.service';
@Component({
...
providers: [
{ provide: ConfigService, useClass: ConfigServiceImpl }
//{ provide: ConfigService, useClass: ConfigServiceImpl2 }
]
})
export class AppComponent {
}
You can then provide the service with different implementations.
Upvotes: 1
Reputation: 1642
TypeScript doesn't produce any symbols for interfaces, but you're going to need a symbol of some sort to make dependency injection work.
Solution: use OpaqueTokens as your symbol, assign a class to that token via the provide() function, and use the Inject function with that token in the constructor of your class.
In the example here, I've assumed that you want to assign PostsServiceImpl to PostsService in the Posts component, because it's easier to explain when the code is all in the same place. The results of the provide() call could also be put into the second argument of angular.bootstrap(someComponent, arrayOfProviders), or the provide[] array of a component higher up the hierarchy than the Posts component.
In components/posts/posts.service.ts:
import {OpaqueToken} from "@angular/core";
export let PostsServiceToken = new OpaqueToken("PostsService");
In your constructor:
import {PostsService, PostsServiceImpl, PostsServiceToken} from 'components/posts/posts.service';
import {provide} from "@angular/core";
@Component({
selector: 'posts',
providers: [provide(PostsServiceToken, {useClass: PostsServiceImpl})]
})
export class Posts {
private postsServices: PostsService;
constructor(
@Inject(PostsServiceToken)
postsService: PostsService
) {
console.log('Loading the Posts component');
this.postsServices = postsService;
...
}
...
}
Upvotes: 3
Reputation: 99
You have to keep in mind that your class may (and should) depend on abstractions, but there is one place where you can't use abstraction : it's in the dependency injection of Angular.
Angular must know what implementation to use.
Example :
/// <reference path="../../../../../_reference.ts" />
module MyModule.Services {
"use strict";
export class LocalStorageService implements ILocalStorageService {
public set = ( key: string, value: any ) => {
this.$window.localStorage[key] = value;
};
public get = ( key: string, defaultValue: any ) => {
return this.$window.localStorage[key] || defaultValue;
};
public setObject = ( key: string, value: any ) => {
this.$window.localStorage[key] = JSON.stringify( value );
};
public getObject = ( key: string ) => {
if ( this.$window.localStorage[key] ) {
return JSON.parse( this.$window.localStorage[key] );
} else {
return undefined;
}
};
constructor(
private $window: ng.IWindowService // here you depend to an abstraction ...
) { }
}
app.service( "localStorageService",
["$window", // ... but here you have to specify the implementation
Services.LocalStorageService] );
}
So in your test you can use mocks easily as all your controllers/services etc depend on abstractions. But for the real application, angular needs implementations.
Upvotes: 4
Reputation: 276057
Any practical ideas how I can use interface based programming w/ TypeScript & Angular 2
Interfaces are erased at runtime. In fact decorators don't support interfaces either. So you are better off using something that does exist at runtime (i.e. implementations).
Upvotes: 6