概要
前回の続きです。
前回の要件に加え、今回対応したいのは以下です。
- 開く時にパラメータを渡して、Modal内のComponentで利用したい
例えばエラーメッセージを渡して上部に通知バーを表示するなどを作る時にこういった仕組みが必要になると思います。
今回は統一した完了ダイアログComponentを使うが、メッセージだけ切り替えたいという要件を実現します。
環境
- angular 2.4.7
- angular-cli 1.0.0-beta.32.3
成果物
今回の成果物は以下です。
ブログの説明でよく分からない時は参考にしてください。
フォルダ構造
. ├── app.component.css ├── app.component.html ├── app.component.spec.ts ├── app.component.ts ├── app.module.ts ├── complete │ ├── complete.component.css │ ├── complete.component.html │ ├── complete.component.spec.ts │ └── complete.component.ts └── modal ├── modal.component.css ├── modal.component.html ├── modal.component.spec.ts ├── modal.component.ts ├── modal.service.spec.ts └── modal.service.ts
実装
complete.component.ts
export const COMPLETE_TEXT_TOKEN = new OpaqueToken('complete.text'); @Component({ selector: 'app-complete', templateUrl: './complete.component.html', styleUrls: ['./complete.component.css'] }) export class CompleteComponent { public test: string; constructor(@Inject(COMPLETE_TEXT_TOKEN) t: string) { this.text = t; } }
今回呼び出すComponentへ文字列を渡したいので、constructorのところに@Inject
の引数を持たせます。
この時serviceやcomponentなどのclass型であれば、DIするときのprovider tokenを宜しくやってくれるのですが、stringやobjectの場合はOpaqueToken
を使って自分でtokenを発行する必要があります。
modal.service.ts
open(data: any, provider: Provider): void { if (!data) { return; } const providers = ReflectiveInjector.resolve([provider]); const injector = ReflectiveInjector.fromResolvedProviders(providers, this.vcr.parentInjector); const factory = this.resolver.resolveComponentFactory(data); const component = this.vcr.createComponent(factory, this.vcr.length, injector);
今回は開く時に引数をもたせます。
注意として、vcr
のcreateComponent
が引数に持つInjector
は、vcr
のparentInjector
をベースにComponentに渡したいproviderをくっつけてInjectします。
もし直接resolveAndCreate([{ provide: TOKEN, useValue: 'xxx' }])
でInjectorを作って入れてみるとNo provider for ViewUtils
とエラーが出ます。
これはどうしてかというと、createComponent
は内部でComponentFactory
のcreate
というメソッドを使用しているのですが、これはViewUtils
というproviderを持ったinjectorでなくてはいけません。
create( injector: Injector, projectableNodes: any[][] = null, rootSelectorOrNode: string|any = null): ComponentRef<C> { const vu: ViewUtils = injector.get(ViewUtils); if (!projectableNodes) { projectableNodes = []; } const hostView: AppView<any> = new this._viewClass(vu, null, null, null); return hostView.createHostView(rootSelectorOrNode, injector, projectableNodes); }
ref: angular/component_factory.ts at ab26b6518d16e54d198031c9994399cc29e270f1 · angular/angular · GitHub
ちなみに前回のようにつけない場合は勝手にvcr.parentInjector
を使用するコードになっているので問題ないです。
createComponent<C>( componentFactory: ComponentFactory<C>, index: number = -1, injector: Injector = null, projectableNodes: any[][] = null): ComponentRef<C> { const s = this._createComponentInContainerScope(); const contextInjector = injector || this._element.parentInjector; const componentRef = componentFactory.create(contextInjector, projectableNodes); this.insert(componentRef.hostView, index); return wtfLeave(s, componentRef); }
ref: angular/view_container_ref.ts at c33fda260726f5c050a75fc01fbd7a74800f8e37 · angular/angular · GitHub
また、今回の理由とは別ですが、パフォーマンス的にも
var injector = ReflectiveInjector.resolveAndCreate([Car, Engine]);
より
var providers = ReflectiveInjector.resolve([Car, Engine]); var injector = ReflectiveInjector.fromResolvedProviders(providers);
という書き方を公式のAPIドキュメントは推奨をしています。
app.component.ts
register(): void { const provider = { provide: COMPLETE_TEXT_TOKEN, useValue: '登録が完了しました' }; this.modal.open(CompleteComponent, provider); } send(): void { const provider = { provide: COMPLETE_TEXT_TOKEN, useValue: '送信が完了しました' }; this.modal.open(CompleteComponent, provider); }
呼び出す際にprovider
も一緒に渡すようにしています。
動作確認
Registerボタンをクリックします。
「登録が完了しました」と出てます。もう片方は
「送信が完了しました」と出てます。メッセージを切り替えられる事がわかります。