Goalist Developers Blog

Angular2でFlexのViewStackを作ってみる

全国のFlexファンの皆様、お久しぶりです!
ゴーリスト開発の盛次(モリツグ)です。

前回のAngular2とFlexを比較してみたの影響で全国に90人はいると思われるFlex愛好者たちがAngular2に目覚めてくれたことだと思います。

今日は以下のような方に向けた内容になります。
・Angular2で動的にコンポーネントを追加したい方
・Angular2でもaddChildしてselectedIndexしたくて我慢できない方

ViewStackのデモを触ってみる

Flex経験者でない方にViewStackの説明が必要ですね!
ViewStackとはコンポーネントを格納するためのコンテナです、複数コンポーネントを格納できますが、表示するのは現在選択中のものだけです。

Angular2では標準で画面遷移の仕組みが用意されており、遷移時にServiceを差し込めたりと非常に便利です。
しかし、動的にaddChildしてselectedIndex=0したい人は少なくないと思います。
むしろFlex愛好家は「なんでViewStackないん?」と思っているはずです。
とりあえず完成品をご覧ください。ViewStackデモ

リンク先のplunkerでは以下のような画面が表示されます。

f:id:t-moritsugu:20170222175904p:plain

動作は以下のようになっています。
・ViewStackには初期化時に3つのコンポーネントを格納
・左側のボタン3つに対応したコンポーネントを追加
・selectで選んだコンポーネントだけを表示
・removeボタンを押すと選択中のコンポーネントを削除

コンポーネントを動的に追加する方法

  1. 動的に追加したいコンポーネントを@NgModuleのdeclarationsだけでなくentryComponentsにも追記する。
  2. ComponentFactoryResolverに追加したいコンポーネントを渡して目的のクラスのComponentFactoryを作成する。
  3. ComponentFactoryをViewContainerRef.createComponentの引数にする。

src/app.ts

@NgModule({
  declarations: [.....],
  imports: [....],
  bootstrap: [App ],
  // 動的に追加するコンポーネントはここに指定
  entryComponents: [ChildA, ChildB, ChildC]   
})
export class AppModule {}

src/app/viewstack/ViewStack.ts

@Component({
  selector: 'view-stack',
  providers: [],
  template: `
    <div #container>
    </div>
  `
})

export class ViewStack implements AfterViewInit {
  
  @ViewChild('container', {read: ViewContainerRef}) 
  private container:ViewContainerRef;
  
  public constructor(
    private _componentFactoryResolver: ComponentFactoryResolver
    //, private _viewContainerRef: ViewContainerRef
  )
  {}

  public addChild<T>(childClass: Type<T>, index:number=-1):void {
    
    const componentFactory:ComponentFactory<T>
      = this._componentFactoryResolver.resolveComponentFactory(childClass);

    const componentRef:ComponentRef<T> 
      = this.container.createComponent(componentFactory);
    
  }
}

ViewContainerRef.createComponentがどの要素に対して作成したコンポーネントを追加するのかが重要です。
このソースを見る限りthis.container.createComponentしているために以下のようなhtmlになりそうです。

<app>
  ...
  <view-stack>
    <div>
      <child-a>...
      <child-b>...
      <child-c>...
    </div>
  </view-stack>
  ...
</app>

しかし、実際は以下のようになります。

<app>
  ...
  <view-stack>
    <div>
    </div>
    <child-a>...
    <child-b>...
    <child-c>...
  </view-stack>
  ...
</app>

ViewContainerRef.createComponentは親の要素に作成したコンポーネントを追加するようです。
ダミー的に<div #container>を利用しているのはこのためです。
仮にconstructorでinjectしたViewContainerRefを利用した場合、以下のようなhtmlになります。

<app>
  ...
  <view-stack>
    <div>
    </div>
  </view-stack>
 <child-a>...
  <child-b>...
  <child-c>...
  ...
</app>

まとめ

addChildしてselectedIndexしたかったのでAngular2でViewStackを作ってみました。
*ngForと[ngSwitch]を利用すれば別の実装手段もありますが、一番疎結合なのはentryComponentsを使う今回の方法だと思います。
Flex経験者を取り込んでAngular2の人口が増えることを願って書いています。