Master PG

プログラムし続ける...

【Polymer + TypeScript】PolymerTSを使ってみる


目次


概要

PolymerをTypeScriptで記述するためのライブラリPolymerTSを紹介したいと思います。自力でPolymerをTypeScriptで記述しようとすると、TypeScriptの良いところが引き出せない状態になりがちですが、PolymerTSを使用すると、Polymerの良い所、TypeScriptの良い所、を引き出しつつプログラミングできると思います。


インストール

インストールは次のコマンドで行います:

bower install polymer-ts

インストールすると「bower_components/polymer-ts」に次のファイルが取得されます:

  • polymer-ts.html: メインのhtmlファイルから<link rel="import">でこのファイルをインポートします。
  • polymer-ts.min.html: polymer-ts.htmlのminfyバージョンです。
  • polymer-ts.d.ts: 自身で作成するTypScriptファイルから/// <reference path="...">でこのファイルを参照します。
  • polymer-ts.ts: デバッグのためのTypeScriptのソースファイルです。
  • polymer-ts.js: <script src="">でPolymerTSをインクルードしたい場合のためのJavaScriptファイルです。
  • polymer-ts.min.js: polymer-ts.jsのminifyバージョンです。


PolymerTSの使い方

ここではPolymerTSを使うのに必要な最低限の手順を示します。

メインのhtmlファイル (例: index.html) のheadセクションの例です:

【index.html】

<head>
  <script src="bower_components/webcomponentsjs/webcomponents-lite.js"></script>   
  <link rel="import" href="bower_components/polymer/polymer.html">

  <!-- PolymerTSをインポート -->
  <link rel="import" href="bower_components/polymer-ts/polymer-ts.html"> 

  <!-- 自身のカスタムエレメントをインポート -->
  <link rel="import" href="elements/my-element.html">   

  <!-- 自身のアプリケーションの読み込み -->
  <script src="myapp.js"></script>            
</head>

自身で作成するカスタムエレメントの例です:

【elements/my-element.html】

<dom-module id="my-element">
  <!-- ... カスタムエレメントの内容 ... -->
</dom-module>
<!-- TypeScripがコンパイルしたカスタムエレメントのjsファイルを指定 -->
<script type="text/javascript" src="my-element.js"></script>

カスタムエレメントのTypeScriptコードの例です:

【elements/my-element.ts】

/// <reference path="../bower_components/polymer-ts/polymer-ts.d.ts" />

@component('my-element')
class MyElement extends polymer.Base {
}
// カスタムエレメントの定義のあとに、register()でカスタムエレメントをブラウザに登録
MyElement.register();


Decorator


@component(tagName)

@componentでは、カスタムエレメントのHTMLと、TypeScriptのクラスを関連付けます:

@component('my-element')
class MyElement extends polymer.Base {
}


カスタムエレメントがネイティブHTMLエレメントを継承する場合、第2引数にネイティブHTMLエレメントの名前を指定します (ネイティブHTMLエレメントの継承は次で説明する@extendでも行なえます) :

@component('my-input', 'input')
class MyInput extends polymer.Base {
}


@extend(tagName)

Note: ネイティブHTMLエレメント継承についての詳細は「カスタムエレメントの登録とライフサイクル - ネイティブHTMLエレメントの継承」を参照ください。

@extendでネイティブHTMLエレメントを継承する場合、引数にネイティブHTMLエレメントの名前を指定します:

@component('my-input') 
@extend('input')
class MyInput extends polymer.Base {
}

ネイティブHTMLエレメントを継承することはできましたが、継承した<input>エレメントのvalueにアクセスしようとすると、TypeScriptでvalueプロパティがないというコンパイルエラーが発生します。これを解決する方法を見てみましょう。

TypeScriptでは、<input>エレメントに対してHTMLInputElementというクラスが用意されており、この中でvalueは次のように定義されています:

interface HTMLInputElement extends HTMLElement, MSDataBindingExtensions {
  ...

  /**
    * Returns the value of the data at the cursor's current position.
    */
  value: string;
}

このvalueの定義を、継承先のクラスにコピーしてあげれば、継承先のクラスでvalueを使用できるようになります:

@component('my-input')
@extend('input')
class MyInput extends polymer.Base {

  value: string;

  ready() {
    this.value = 'My input';
  }
}


@property(def)

Note: Polymerのプロパティについての詳細は「宣言型プロパティ(Declared properties)」を参照ください。

@propertyは、Polymerのプロパティを作成します。

引数のdefには次のオブジェクトを指定します:

{
  // Boolean, Date, Number, String, Array, Object を指定
  type?: any;
  // プロパティのデフォルト値を指定
  value?: any;
  // プロパティの値が変更された際、プロパティと一致する属性にも値を設定するかの有無
  reflectToAttribute?: boolean;
  // プロパティを読み取り専用にするかの有無
  readonly?: boolean;
  // プロパティの値が変更された際、"<property>-changed"イベントを発火するかの有無
  notify?: boolean;
  // Computed関数を指定
  computed?: string;
  // オブザーバー関数を指定
  observer?: string;
}

次は一般的な例です:

@property({type: Number, value: 42})
myprop: number;

次の例では、上記のようにデフォルト値の設定でvalueを使用せず、プロパティに直接設定しています:

@property()
myProp = 42;

次の例では、デフォルト値をコンストラクタで設定しています:

@property({type: Number})
myProp:number;

constructor() {
  super();
  this.myprop = 42;
}


Computed関数とオブザーバー関数はcomputedobserverで指定できますが、この方法以外にも@computed@observeで指定することができます。


@observe(propList)

Note: プロパティ変更監視の詳細については「宣言型プロパティ(Declared properties) - プロパティ変更の監視」を参照ください。

@observeはオブザーバー関数の設定を行います。

引数に監視対象のプロパティを1つだけ指定した場合、オブザーバー関数の引数には新しい値と古い値が設定され、呼び出されます (詳細はここを参照) 。

@observe('name')
nameChanged(newName, oldName) {
  // ... 
}

監視対象のプロパティを複数指定した場合、オブザーバー関数の引数に古い値は設定されず、新しい値だけが設定されます (詳細はここを参照) :

@observe('firstName, lastName')
fullnameChanged(newFirstName, newLastName) {
  // ... 
}

監視対象のオブジェクトにワイルドカードを指定し、オブジェクト全てのプロパティ (ここではfirstNamelastName) を監視することができます (詳細はここを参照) :

class User {
  firstName:string;
  lastName:string;
}

@component('my-element')
class MyElelement extends polymer.Base {
  @property({type: User})
  user: User;

  @observe('user.*')
  userChanged(changeRecord):void {
    // ...
  }
}

配列の変更 (アイテムの追加/削除 など) を監視するには、配列へのパスにsplicesを付加した文字列を引数に設定します (詳細はここを参照) :

@component('my-element')
class MyElelement extends polymer.Base {
  @property({type: Array})
  users:User[] = [];

  @observe('users.splices')
  usersChanged(changeRecord) {
    // ...
  }
}

配列の変更 (アイテムの追加/削除 など) に加え、配列アイテムのプロパティも監視するには、配列へのパスに*を付加した文字列を引数に設定します (詳細はここを参照) :

@component('my-element')
class MyElelement extends polymer.Base {
  @property({type: Array})
  users:User[] = [];

  @observe('users.*')
  usersChanged(changeRecord) {
    // ...
  }
}


@computed

Note: Computedプロパティの詳細については「宣言型プロパティ(Declared properties) - Computedプロパティ」を参照ください。

@computedはComputedプロパティの設定を行います。

Computedプロパティを設定する一番簡単な方法は、Computedプロパティとなる関数の引数に、関連するプロパティを指定し、@computedを付けることです:

firstName: string;
lastName: string;

@computed()
fullName(firstName, lastName): string {
  return firstName + ' ' + lastName;
}

@computedの引数では、@propertyの引数と同じ内容のオブジェクトを受け入れます。次はその例です:

@computed({type: String})
fullName(firstName, lastName): string {
  return firstName + ' ' + lastName;
}

@computed@propertyのショートカットです。次はComputedプロパティを@propertyで設定した例です:

firstName: string;
lastName: string;

@property({computed: 'computeFullName(firstName, lastName)'})
fullName: string;

computeFullName(firstName, lastName) {
  return firstName + ' ' + lastName;
}


@listen(eventName)

Note: Polymerのイベントについての詳細は「イベント」を参照ください。

@listenはイベントリスナの設定を行います。引数にはイベントを指定し、このイベントとイベントハンドラを関連付けます。

次の例では、htmlでid="sendButton"と指定されたエレメントが、タップ (クリック) されることによりtapイベントが発火され、sendButtonOnTap()ハンドラが呼び出されることになります:

@listen('sendButton.tap')
sendButtonOnTap(event) {
  // ...
}


@behavior(className)

Note: PolymerのBehaviorについての詳細は「Behaviors」を参照ください。

@behaviorはBehaviorを継承するために使用します。

@behaviorの引数に指定するBehaviorは、polymer.Baseの継承の有無を問いません。下記例のMyBehaviorpolymer.Baseを継承していますが、polymer.Baseの機能を必要としない場合は継承する必要はありません。

次の例では、my-element自体がタップ (クリック) されると、ブラウザのメッセージボックスにhelloの値が表示されます:

class MyBehavior extends polymer.Base {
  @listen('tap')
  onTap(event) {
    this.messageBox(this.hello);
  }

  @property({type: String, value: 'Hello!'})
  hello: string;

  messageBox(message: string): void {
    alert(message);
  }
}
@component('my-element')
@behavior(MyBehavior)
class MyElelement extends polymer.Base {
  // ...
}

Note: Behaviorクラスのプロパティ値設定には注意が必要です。MyBehaviorhelloプロパティの初期値は、上記のように@propertyで設定するか、ライフサイクルコールバック (ready, attached など) で設定してください。helloプロパティの定義で直接、またはconstructor()の中で値を設定しても、実際に値が設定されません (2015/11/04に確認した結果) 。

上記のままでは、MyElelementの中で、継承したMyBehaviorhelloまたはmessageBox()を使おうとすると、TypeScriptでこれらのプロパティまたはメソッドが存在しないというコンパイルエラーが発生します。このような場合は、MyElelementに空のhelloまたはmessageBox()を定義してください:

@component('my-element')
@behavior(MyBehavior)
class MyElelement extends polymer.Base {
  // 空のhelloを定義する
  hello: string;
  // 空のmessageBox()を定義する
  messageBox: (message: string) => void;

  greeting(): void {
    // 継承したMyBehaviorのhelloとmessageBox()を使用できる
    this.messageBox(this.hello + ' I am my-element');
  }
}


Behaviorは複数継承することできます。MyBehavior1MyBehavior2で同名のプロパティまたはメソッドが存在する場合、上の方が優先されます。つまりここではMyBehavior1の方が優先されることになります:

@component('my-element')
@behavior(MyBehavior1)
@behavior(MyBehavior2)
class MyElelement extends polymer.Base {
  // ...
}


@hostAttributes(attributesObject)

Note: カスタムエレメントの属性についての詳細は「カスタムエレメントの登録とライフサイクル - カスタムエレメントの属性」を参照ください。

カスタムエレメント自身に存在する属性の設定、または独自属性を追加したい場合、@hostAttributesを使用します。

次の例では、style属性の設定と、string-attributeという独自属性の設定を行っています:

@component('my-element')
@hostAttributes({
  'style': 'color: red;',
  'string-attribute': 'StringAttributeValue'
})
class MyElement extends polymer.Base {
  // ...
}


コンストラクタ

PolymerTSのコンストラクタでは、次のことを留意してください:

  • constructor()は、attachedコールバックの前に呼び出されます。
  • コンストラクタに引数を渡したい場合は、factoryImpl()を使用せず、カスタムコンストラクタを作成し、constructor()で引数を受け取るようにします。
  • カスタムエレメントの作成はcreate()メソッドで行います。

次の例は、カスタムコンストラクタで引数を受け取ります。カスタムエレメントの作成にcreate()を使用していることに注意してください:

@component('my-info')
class MyInfo extends polymer.Base {
  private someInfo: string;

  constructor(someInfo: string) {
    super();
    this.someInfo = someInfo;
  }
}
MyInfo.register();
// create()に引数を設定すると、それがMyInfoのカスタムコンストラクタに渡される
var el = MyInfo.create('hello world');

// 作成したMyInfoをbodyに追加
document.body.appendChild(el);


コーディングだけでカスタムエレメントを作成する

htmlファイルを使用せずに、TypeScriptコードだけでカスタムエレメントを作成することができます。

次の例では、@template@styleで、エレメントのテンプレートとスタイルを定義しています:

@component('my-example')
// <dom-module>...</dom-module>に入るべき内容を引数で渡す
@template(`<div>This element has been created completely from code</div>`)
// <style>...</style>に入るべき内容を引数で渡す
@style(`:host { display: block; } div { color: red; }`)
class MyExample extends polymer.Base {
  // ...
}
MyExample.register();


Decoratorを使用せずにカスタムエレメントを作成する

TypeScriptのバージョンが1.5より低いとDecoratoreを使用できません。このような場合、Polymerと似たシンタックスでカスタムエレメントを作成することができます:

class MyElement extends polymer.Base {
   is = 'my-element';
   properties = {
      myprop: { value: 42 }
   };
   // ...
}