使用 TypeScript、Angular 2 和 SystemJS 进行基于接口的编程
Interface based programming with TypeScript, Angular 2 & SystemJS
我目前正在尝试清理一些代码,以便针对接口而不是针对实现进行编程,但不知道如何去做。
更具体地说,我正在使用:
* TypeScript 1.5.0 beta -> 转译为 ES5 / commonjs
* SystemJS 加载模块
我目前尝试使用如下外部模块:
posts.service.ts 文件:
///<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>{
...
}
并且该模块在 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;
...
}
...
}
上面的代码编译正确,但基本上我的问题归结为 Angular 不会在 Posts 组件中注入 PostsServiceImpl 的实例。
当然只是因为我没有找到正确的方法declare/export/import一切
没有 export interface PostsService ...
,我得到 TS 编译错误,因为我在帖子控制器中使用它。
没有 export var PostsService:PostsServiceImpl;
我在 @View 装饰器的 viewInjector
中得到 TS 编译错误
在生成的 js 代码中我只找到 exports.PostsService;
这是因为导出变量而添加的...我知道接口在运行时消失了。
所以我对这一切有点迷茫。关于如何使用基于接口的编程和 TypeScript & Angular 2?
的任何实用想法
Any practical ideas how I can use interface based programming w/ TypeScript & Angular 2
接口在运行时被擦除。事实上装饰器也不支持接口。所以你最好使用运行时确实存在的东西(即实现)。
你必须记住,你的 class 可能 (并且应该) 依赖于抽象 ,但是有一个不能使用抽象的地方:它在 Angular 的依赖注入中。
Angular 必须知道要使用什么实现。
示例:
/// <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] );
}
因此,在您的测试中,您可以轻松地使用模拟,因为所有 controllers/services 等都依赖于抽象。但对于实际应用,angular 需要实现。
TypeScript 不会为接口生成任何符号,但您将需要某种符号来使依赖注入起作用。
解决方案:使用 OpaqueTokens 作为符号,通过 provide() 函数将 class 分配给该标记,然后在 class 的构造函数中使用带有该标记的 Inject 函数。
在此处的示例中,我假设您要将 PostsServiceImpl 分配给 Posts 组件中的 PostsService,因为当代码都在同一位置时更容易解释。 provide() 调用的结果也可以放入 angular.bootstrap(someComponent, arrayOfProviders) 的第二个参数,或者比 Posts 组件层次更高的组件的 provide[] 数组。
在components/posts/posts.service.ts:
import {OpaqueToken} from "@angular/core";
export let PostsServiceToken = new OpaqueToken("PostsService");
在你的构造函数中:
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;
...
}
...
}
在打字稿中你可以实现类。
例子
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 {
}
然后您可以提供具有不同实现的服务。
我目前正在尝试清理一些代码,以便针对接口而不是针对实现进行编程,但不知道如何去做。
更具体地说,我正在使用: * TypeScript 1.5.0 beta -> 转译为 ES5 / commonjs * SystemJS 加载模块
我目前尝试使用如下外部模块:
posts.service.ts 文件:
///<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>{
...
}
并且该模块在 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;
...
}
...
}
上面的代码编译正确,但基本上我的问题归结为 Angular 不会在 Posts 组件中注入 PostsServiceImpl 的实例。
当然只是因为我没有找到正确的方法declare/export/import一切
没有 export interface PostsService ...
,我得到 TS 编译错误,因为我在帖子控制器中使用它。
没有 export var PostsService:PostsServiceImpl;
我在 @View 装饰器的 viewInjector
在生成的 js 代码中我只找到 exports.PostsService;
这是因为导出变量而添加的...我知道接口在运行时消失了。
所以我对这一切有点迷茫。关于如何使用基于接口的编程和 TypeScript & Angular 2?
的任何实用想法Any practical ideas how I can use interface based programming w/ TypeScript & Angular 2
接口在运行时被擦除。事实上装饰器也不支持接口。所以你最好使用运行时确实存在的东西(即实现)。
你必须记住,你的 class 可能 (并且应该) 依赖于抽象 ,但是有一个不能使用抽象的地方:它在 Angular 的依赖注入中。
Angular 必须知道要使用什么实现。
示例:
/// <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] );
}
因此,在您的测试中,您可以轻松地使用模拟,因为所有 controllers/services 等都依赖于抽象。但对于实际应用,angular 需要实现。
TypeScript 不会为接口生成任何符号,但您将需要某种符号来使依赖注入起作用。
解决方案:使用 OpaqueTokens 作为符号,通过 provide() 函数将 class 分配给该标记,然后在 class 的构造函数中使用带有该标记的 Inject 函数。
在此处的示例中,我假设您要将 PostsServiceImpl 分配给 Posts 组件中的 PostsService,因为当代码都在同一位置时更容易解释。 provide() 调用的结果也可以放入 angular.bootstrap(someComponent, arrayOfProviders) 的第二个参数,或者比 Posts 组件层次更高的组件的 provide[] 数组。
在components/posts/posts.service.ts:
import {OpaqueToken} from "@angular/core";
export let PostsServiceToken = new OpaqueToken("PostsService");
在你的构造函数中:
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;
...
}
...
}
在打字稿中你可以实现类。
例子
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 {
}
然后您可以提供具有不同实现的服务。