Dependency Injection
今回はAngular2 DartにおけるDependency Injection(DI)についての基本編です。
こちらのエントリーにもあるように今後DI周りは結構修正がありそうです。
Angular Dartのハイレベルロードマップ
現時点では、私たちはDagger2を基礎にした新しいDIとComponent APIを開発中であり、それはDartの強力な静的型解析とレキシカルスコープを活かしたものになる。
今後APIレベルで変更が発生した場合はその都度更新できるように頑張ります。
DI自体の概念等に関しては説明を省きます。
今回のソースはこちら
単純なDI
Angularの世界ではInjectされる側のクラスの事を単にServiceと呼ぶのが通例のようです。
あるComponentに対してServiceをInjectしたい場合。
まずはServiceクラス。Injectableアノテーションの定義が必要です。注意点としては@Injectable
ではなく@Injectable()
と()
が必ず必要な点です。
@Injectable()
class SimpleAppService {
Future<List<String>> getMessages() async => ["message01","message02","message03"];
}
また、上記は文字列をそのままベタ書きしてますがこういうのは基本的に外部ストレージ(RestAPIとかLocalStrage)から取得することが多いので非同期処理にするのが良いとされています。最終的にDartが出力するのはJSですし、JSはシングルスレッドなのでブロッキングが発生するとブラウザが何も操作を受け付けなくなる可能性があるので(Dartもシングルスレッドモデルですが)
次はComponentです。
@Component(
selector: 'app-sub1',
templateUrl: 'app_sub1.html',
providers: const[SimpleAppService])
class AppSub1 implements OnInit{
List<String> messages;
final SimpleAppService _appService;
AppSub1(this._appService);
Future<Null> initMessages() async {
messages = await _appService.getMessages();
}
@override
void ngOnInit() {
initMessages();
}
}
ポイントは
- Componentアノテーション内のproviders属性
- コンストラクターインジェクション
- OnInitとngOnInit
あたりでしょうか。
providers属性
ここにComponent内で使用するServiceクラスを定義することでAngular2がこのComponentを生成するタイミングでInjectしてくれるようになります。また、
const[SimpleAppService]
は
const [const Provider(SimpleAppService, useClass: SimpleAppService)]
のシンタックスシュガーになります。
コンストラクターインジェクション
DIの世界では様々なInject方法があるのですがAngular2 Dartではコンストラクターインジェクションを提供しています。ですので上記providersにて定義したServiceをコンストラクターにて受け取るための処理が必要になります。
final SimpleAppService _appService;
AppSub1(this._appService);
OnInitとngOnInit
OnInitをimplementsしてngOninitを実装し、Serviceから取得したデータでフィールドを初期化します。
コンストラクタ内で行う事も可能ではありますがインスタンスの生成とComponentの初期化というのは別で考えたい場合はインスタンス生成処理以外の事はOnInitをimplementsするのがよいでしょう。
DIのネスト
ある程度のアプリケーションを作成する場合はServiceクラスに別のサービスをInjectさせたい事が出てくると思います(クライアントサイドDDDとか)
その場合には下記のようになります。
@Injectable()
class NestedAppService {
RequestService _requestService;
NestedAppService(this._requestService);
Future<List<String>> getMessages() async => _requestService.getStrings();
}
@Injectable()
class RequestService {
Future<List<String>> getStrings() async => ["data01","data02"];
}
NestedAppServiceクラスにRequestServiceをInjectする形です。
RequestSerciceをInjectするNestedAppServiceクラスにはComponentの時と同じようにコンストラクターインジェクションさせるようにします。
Component側はprovidersの定義が2つになります(NestedAppService, RequestService)
この時にRequestServiceの定義を忘れるとエラーになります。
@Component(
selector: 'app-sub2',
templateUrl: 'app_sub2.html',
providers: const[NestedAppService, RequestService])
class AppSub2 implements OnInit{
List<String> messages;
final NestedAppService _appService;
AppSub2(this._appService);
Future<Null> initMessages() async {
messages = await _appService.getMessages();
}
@override
void ngOnInit() {
initMessages();
}
}
まとめ(雑感)
DIかつフィールドインジェクションに慣れているとこのコンストラクターインジェクションと定義の方法にはちょっと面倒臭さを感じてしまうかもしれません。このあたりはDartというかJavaScript実行環境を考慮してのアーキテクチャーなんでしょうね。今後もっと簡潔かつわかりやすい定義になってれるといいなぁと思います。
次回
次回はServiceの一括定義と実装の切り替えに関して書いていこうと思います。