web-dev-qa-db-ja.com

TypeScript、Angular 2&SystemJSによるインターフェイスベースのプログラミング

私は現在、実装ではなくインターフェイスに対してプログラムするために、いくつかのコードをクリーンアップしようとしていますが、その方法を理解できません。

より具体的には、私は以下を使用しています:* TypeScript 1.5.0ベータ版-> 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はPostsServiceImplのインスタンスをPostsコンポーネントに挿入しないという事実が原因です。

もちろん、それはすべてを宣言/エクスポート/インポートする正しい方法が見つからないからです

export interface PostsService ...がないと、postsコントローラーで使用するため、TSコンパイルエラーが発生します。

export var PostsService:PostsServiceImpl;がないと、@ ViewデコレータのviewInjectorでTSコンパイルエラーが発生します

生成されたjsコードでは、エクスポート変数のために追加されたexports.PostsService;のみが見つかります...実行時にインターフェイスが消えることを知っています。

だから私はこのすべてで少し迷っています。 TypeScript&Angular 2?

8
dSebastien

TypeScript&Angular 2を使用したインターフェイスベースのプログラミングの使用方法に関する実用的なアイデア

インターフェースは実行時に消去されます。実際、デコレータはインターフェースもサポートしていません。したがって、実行時に存在するもの(つまり、実装)を使用する方がよいでしょう。

6
basarat

あなたのクラスは(そしてすべきである)は抽象化に依存するであることを覚えておく必要がありますが、抽象化は使用できません。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] );
}

したがって、すべてのコントローラー/サービスなどが抽象化に依存しているため、テストではモックを簡単に使用できます。しかし実際のアプリケーションでは、angularは実装が必要です。

4

TypeScriptはインターフェイスのシンボルを生成しませんが、依存関係注入を機能させるには、何らかのシンボルが必要になります。

解決策:シンボルとしてOpaqueTokensを使用し、provide()関数を介してそのトークンにクラスを割り当て、クラスのコンストラクターでそのトークンを使用してInject関数を使用します。

この例では、PostsServiceImplをPostsコンポーネントのPostsServiceに割り当てることを想定しています。これは、コードがすべて同じ場所にある場合を説明する方が簡単だからです。 prepare()呼び出しの結果は、angular.bootstrap(someComponent、arrayOfProviders)の2番目の引数、またはPostsコンポーネントよりも上位のコンポーネントのprepare []配列にも入れることができます。

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;
        ...
    }

    ...
}    
3
PotatoEngineer

TypeScriptでは、クラスを実装できます。

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 {
}

その後、さまざまな実装でサービスを提供できます。

1
Matthias