【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関数とオブザーバー関数はcomputed
とobserver
で指定できますが、この方法以外にも@computedと@observeで指定することができます。
@observe(propList)
Note: プロパティ変更監視の詳細については「宣言型プロパティ(Declared properties) - プロパティ変更の監視」を参照ください。
@observe
はオブザーバー関数の設定を行います。
引数に監視対象のプロパティを1つだけ指定した場合、オブザーバー関数の引数には新しい値と古い値が設定され、呼び出されます (詳細はここを参照) 。
@observe('name') nameChanged(newName, oldName) { // ... }
監視対象のプロパティを複数指定した場合、オブザーバー関数の引数に古い値は設定されず、新しい値だけが設定されます (詳細はここを参照) :
@observe('firstName, lastName') fullnameChanged(newFirstName, newLastName) { // ... }
監視対象のオブジェクトにワイルドカードを指定し、オブジェクト全てのプロパティ (ここではfirstName
とlastName
) を監視することができます (詳細はここを参照) :
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
の継承の有無を問いません。下記例のMyBehavior
はpolymer.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クラスのプロパティ値設定には注意が必要です。
MyBehavior
のhello
プロパティの初期値は、上記のように@property
で設定するか、ライフサイクルコールバック (ready
,attached
など) で設定してください。hello
プロパティの定義で直接、またはconstructor()
の中で値を設定しても、実際に値が設定されません (2015/11/04に確認した結果) 。
上記のままでは、MyElelement
の中で、継承したMyBehavior
のhello
または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は複数継承することできます。MyBehavior1
とMyBehavior2
で同名のプロパティまたはメソッドが存在する場合、上の方が優先されます。つまりここでは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 } }; // ... }