<!DOCTYPE html>
<html>

  <head>
    <base href="." />
    <title>Angular2 動的コンポーネントのサンプル</title>
    
    <!-- [ Load: Environment Library ] -->
    <script src="https://unpkg.com/zone.js@0.7.2/dist/zone.js"></script>
    <script src="https://unpkg.com/zone.js@0.7.2/dist/long-stack-trace-zone.js"></script>
    <script src="https://unpkg.com/reflect-metadata@0.1.3/Reflect.js"></script>
    <script src="https://unpkg.com/systemjs@0.19.31/dist/system.js"></script>
    
    <!-- [ Load: Angular2 Library ] -->
    <script>
      System.config({
        transpiler: 'typescript',
        typescriptOptions: {
          emitDecoratorMetadata: true,
          skipLibCheck: true, // ライブラリのnull許容チェックをスキップ
          strictNullChecks: true, // null許容チェックを有効
        },
        paths: {
          // 'cdn'の定義
          'cdn:': 'https://unpkg.com/',
        },
        map: {
          'typescript': 'cdn:typescript@2.1.4',
          'rxjs'      : 'cdn:rxjs@5.0.1',
          '@angular/core'                    : 'cdn:@angular/core@2.3.0',
          '@angular/common'                  : 'cdn:@angular/common@2.3.0',
          '@angular/compiler'                : 'cdn:@angular/compiler@2.3.0',
          '@angular/platform-browser'        : 'cdn:@angular/platform-browser@2.3.0',
          '@angular/platform-browser-dynamic': 'cdn:@angular/platform-browser-dynamic@2.3.0',
          '@angular/http'                    : 'cdn:@angular/http@2.3.0',
          '@angular/router'                  : 'cdn:@angular/router@3.3.0',
          '@angular/forms'                   : 'cdn:@angular/forms@2.3.0',
        },
        packages: {
          app: { defaultExtension: 'ts' },
          rxjs: { defaultExtension: 'js' },
        },
      });

      System.import('app/main.ts').catch(console.error.bind(console));
    </script>
    
    <style>
      @import url(https://fonts.googleapis.com/css?family=Lato:400,700);
      body{
          font-family: Lato;
      }
    </style>
  </head>

  <body>
    <my-app>loading...</my-app>
  </body>

</html>
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { BrowserModule } from '@angular/platform-browser';
import {
  Component,
  ComponentFactoryResolver,
  EventEmitter,
  NgModule,
  OnInit,
  OnDestroy,
  ViewContainerRef
} from '@angular/core';
import { Observable } from 'rxjs/Rx'; // MEMO: plunkerの場合は'rxjs/Rx'、ローカルでangular-cliの場合は'rxjs'


/**
 * 動的に生成するサンプルコンポーネント
 */
@Component({
  selector: 'app-sample',
  template: `
    <div style="margin: 1rem; padding: 1rem; border: 1px solid #eee;">
      <button (click)="closeButton_Clicked()">CLOSE</button> No. {{_no}}
    </div>
  `,
})
export class SampleComponent implements OnInit, OnDestroy
{
  /** クローズイベント */
  private _closing = new EventEmitter<{}>();
  
  /** コンポーネント生成元から受け取るパラメータ */
  private _no: number = 0;
  
  /** クローズイベントのgetプロパティ */
  public get closing(): Observable<{}>
  {
    return this._closing;
  }
  
  /** コンポーネント生成元から受け取るパラメータのsetプロパティ */
  public set no(value: number)
  {
    this._no = value;
  }
  
  /**
   * Angular Lifecycle Hooks: OnInit
   */
  public ngOnInit(): void
  {
    console.log(`SampleComponent.ngOnInit >> no="${this._no}"`);
  }
  
  /**
   * Angular Lifecycle Hooks: OnDestroy
   */
  public ngOnDestroy(): void
  {
    console.log(`SampleComponent.ngOnDestroy >> no="${this._no}"`);
  }
  
  /**
   * CLOSEボタンのクリックイベントハンドラ
   */
  private closeButton_Clicked(): void
  {
    // クローズイベントを発行
    this._closing.emit({});
  }
}


/**
 * メインAppコンポーネント
 */
@Component({
  selector: 'my-app',
  template: `
    <div style="padding: 1rem; border: 1px solid #eee;">
      <h2>Angular2 Dynamic Component Sample</h2>
      <button (click)="createButton_Clicked()">CREATE</button>
    </div>
  `,
})
export class App
{
  public constructor(
    private _componentFactoryResolver: ComponentFactoryResolver,
    private _viewContainerRef: ViewContainerRef)
  {
  }
  
  /**
   * CREATEボタンのクリックイベントハンドラ
   */
  private createButton_Clicked(): void
  {
    // コンポーネント生成器を初期化
    const componentFactory = this._componentFactoryResolver.resolveComponentFactory(SampleComponent);
    
    // 動的にコンポーネントを生成
    const componentRef = this._viewContainerRef.createComponent(componentFactory);
    
    // 動的に生成したコンポーネントにパラメータを渡す
    componentRef.instance.no = Math.floor(Math.random() * 100); // 0~99のランダム値を渡す
    
    // closingイベントを受けたらコンポーネント破棄する
    componentRef.instance.closing.subscribe(() => {
      componentRef.destroy();
    });
  }
}


/**
 * アプリケーションモジュール
 */
@NgModule({
  imports: [ BrowserModule ],
  declarations: [ App, SampleComponent ], // 独自のcomponent/directiveをdeclarationsに指定する
  bootstrap: [ App ],
  entryComponents: [ SampleComponent ], // 動的に生成するcomponentをentryComponentsに指定する
})
export class AppModule
{
}

platformBrowserDynamic().bootstrapModule(AppModule);