前のページ

次のページ


テンプレート構文 (Template Syntax)

********

我々のAngularアプリケーションはユーザが何を見また何ができるかを管理し、Componentクラスのインスタンス(コンポネント)とそのユーザが面するテンプレートとの関わり合いを通してこれを実現します。

我々の多くはモデル – ビュー - コントローラ (MVC)あるいはモデル – ビュー - ビュー・モデル (MVVM)での経験からコンポネント/テンプレートの二元性には馴染みがあると思います。Angularではコンポネントがコントローラ/ビュー・モデル (controller/viewmodel)の部分の役割を持ち、テンプレートはビューを表現しています。

それでは我々のビューのためのテンプレートを書くのに必要なものを探しましょう。我々はテンプレート構文の基本的な要素たちをここでカバーしてゆきます。





ライブのサンプルソース・コードを見る)を走らせてみましょう。



HTML

HTMLAngularテンプレートの言語です。QuickStartのアプリケーションには純粋なHTMLのテンプレートがありました。(訳者注:201612月時点では異なっていますが)

<h1>
  <a id="hello-angular" class="anchor" href="#hello-angular" aria-hidden="true">
    <span class="octicon octicon-link"></span>
  </a>Hello Angular
</h1>

殆ど総てのHTML構文は有効なテンプレート構文です。<script>要素は顕著な例外で、これは禁止されており、これによりスクリプト注入攻撃のリスクを無くしています。(実際には<script>は単に無視されるだけです。)

一部の合法なHTMLはテンプレートの中ではあまり意味がありません。<html><body>、及び<base>要素は我々のレパートリのなかでは何も有用な役割を持ちません。それ以外のかなりのものは格好の対象となります。

我々のテンプレートのHTML語彙集を、新要素たちと属性たちとして出現するコンポネントたちとディレクティブたちで拡張することができます。以下の節ではデータ・バインディングを介してDOM (Document Object Model)の値を動的にゲット/セットするかを学びます。

それではデータ・バインディングの最初の様式(内挿)にとりかかり、HTMLテンプレートがどれだけリッチなものになるかを見てみましょう。



内挿 (Interpolation)

Angulerガイドの初期のところで我々は二重波括弧 {{ }} に面しました。

<p>My current hero is {{currentHero.firstName}}</p>

我々は計算した文字列をHTML要素タグ間と属性代入内に組み込むために内挿を使います。

<h3>
  {{title}}
  <img src="{{heroImageUrl}}" style="height:30px">
</h3>

二重波括弧間の材料はしばしばコンポネント・プロパティの名前になります。Angularはその名前をそれに対応するコンポネントのプロパティの文字列の値と置き換えます。上の例では、AngulartitleheroImageUrlプロパティたちを計算し、最初に太文字のアプリのタイトルを、次にヒロインのイメージを「空領域内に埋め込み ("fills in the blanks")」ます。

もっと一般的に言えば、二重波括弧間の材料はテンプレート式で、Angularが最初にそれを計算し、次に文字列に変換します。以下の内挿は二重波括弧内に2つの数を加算することでこのポイントを説明しています:

<!-- "The sum of 1 + 1 is 2" -->
<p>The sum of 1 + 1 is {{1 + 1}}</p>

式はホスト・コンポネントのメソッドを呼び出すことができます。次の例ではgetVal()がそうです:

<!-- "The sum of 1 + 1 is not 4" -->
<p>The sum of 1 + 1 is not {{1 + 1 + getVal()}}</p>

Angularは二重波括弧内の総ての式を計算し、その式の結果を文字列に変換し、そして隣接するリテラル文字列にこれらをリンクさせます。最後に、Angularは組み立てた内挿された結果をあるエレメントまたはディレクティブ・プロパティに代入します。

我々はその結果を要素タグ間に挿入している、及びそれを属性たちに代入しているかに見えます。そう考えるのは便利であり、この誤りで悪い結果を受けることは稀です。しかしながらこれは正確には正しくありません。内挿はAngularが属性バインディングに変換する特別な構文で、それを以下に説明します。

しかしながらまず最初に、テンプレート式と文をより詳しく見てみましょう。



テンプレート式 (Template expressions)

テンプレートは値を作り出します。Angularはその式を実行し、それをバインディングのターゲットのプロパティに代入します:このターゲットはHTML要素、コンポネント、あるいはディレクティブです。

我々は{{1 + 1}}と書くときはテンプレート式を内挿括弧内に置きます。属性バインディングの節でテンプレート式は再度出てきますが、この場合はテンプレート式は[property]="expression"のように=シンボルの右側に引用符で囲ったかたちで出てきます。

我々はDartのように見える言語でテンプレート式を書きます。多くのDartの式は合法なテンプレート式ですが、全部がそうではありません。

副作用がある、あるいは副作用を助長するDartの式は禁止されています。それらは以下のものです(訳者注:これはうっかり間違いを起こしそうなので注意が必要です):

  • 代入 (=, +=, -=, …)

  • newまたはconst

  • ;でチェインとなった式

  • 増分または減分演算子 (++ and --)

Dartの構文からの顕著な相違は:

  • Dartの文字列内挿の非対応;例えば"'The title is $title'"の代わりに"'The title is ' + title"を使わねばなりません

  • ビット演算子 | &は非対応

  • | ような新しいテンプレート式演算子



式のコンテキスト (Expression context)

多分もっと驚かされるのは、テンプレート式は静的プロパティたちだけでなく、dart:htmlwindowまたはdocumentのようなトップ・レベルの変数または関数を参照できないということです。これらはprintまたはdart:mathからインポートした関数たちを直接呼ぶことができません。これらは式のコンテキストのメンバたちへの参照だけに制限されています。

式のコンテキストは一般的にはそのコンポネントのインスタンスで、それはバインディング値たちのソースです。

二重波括弧で包まれたtitle{{title}}を見るときは、そのtitleはデータ・バインドされたコンポネントのプロパティだとわかります。 [disabled]="isUnchanged"isUnchangedを見たときは、そのコンポネントのisUnchangedプロパティを我々が参照しているのだとわかります。

通常コンポネント自身は式のコンテキストとなります:この場合テンプレート式は通常そのコンポネントを参照します。

式のコンテキストはそのコンポネント以外のオブジェクトを含むことができます。テンプレート参照変数はそのような代替コンテキスト・オブジェクトのひとつです。



式のガイドライン (Expression guidelines)

テンプレート式はアプリケーションの成否を決めます。どうか以下のガイドラインに従ってください:

これらのガイドラインに対する唯一の例外は、あなたが完全に理解しているという特定の状況下に於いてであるべきです。



見える副作用がないこと

テンプレート式はターゲット・プロパティの値以外いかなるアプリケーションの状態を変えてはいけません。

このルールはAngularの一方向データ・フローというポリシーにとって不可欠です。我々はあるコンポネントの値を読んだときに何か他の表示されている値を変えるかもしれないという心配を決して持つべきではありません。そのビューは単一のレンダリングのパス(rendering pass) 内では安定であるべきです。



早い実行

Angularは我々が考える以上頻繁にテンプレート式を実行します。それらは各キー打鍵あるいは各マウス動作のあとで呼ばれ得ます。式たちはすぐに終了するようにすべきで、そうでないと、時に遅い機器では、そのユーザ経験は足を引っ張られてしまいます。計算が貴重な場合に他の値たちから計算した値たちをキャッシュすることを考えてみてください。



単純性 (Simplicity)

非常に複雑なテンプレート式を書くことは可能ではありますが、そうすべきではありません。

プロパティ名またはメソッド呼び出しが常識的であるべきです。たまのブール否定 (!) OKです。それ以外にはアプリケーションとビジネスのロジックはそのコンポネント自身に閉じ込めてください:コンポネント内にあれば開発とテストがより容易になります。



操作を二重に行っても一度だけ有効になること (Idempotence)

副作用がないこととAngularの変化検出性能を改善するということから、アイデムポンテンス(冪等性)が理想的です。

Angularの用語では、アイデムポンテントな式はその従属値たちが変化しない限り常にまさしく同じことを返します。

従属値たちはそのイベント・ループの一回りの間に変化してはなりません。もしアイデムポンテントな式がStringまたはnumを返すときは、立て続けに2回呼ばれても同じStringまたはnumを返します。もしその式がオブジェクト(Listを含む)返すときは、それは立て続けに2回呼ばれても同じオブジェクト参照を返します。



テンプレート文 (Template statements)

テンプレート文は要素、コンポネント、またはディレクティブのようなバインディング・ターゲットから生起されたイベントに応答します。

我々はテンプレート文をイベント・バインディングの節で見ることになりますが、これは(event)="statement"内にあるように、シンボル = の右側の引用符内に出現します。

テンプレート文は副作用を持ちます。それはユーザ入力から如何に我々がアプリケーションの状態を更新するかということです。そうでなければあるイベントに応答する場所がないことになってしまいます。

イベントに応答することは、Angularの「単方向データ・フロー (unidirectional data flow)」の他面でもあります。このイベント・ループのこの向きの間は、我々は自由になんでもどこでも変えることができます。

テンプレート式と似て、テンプレート文はDartに似た言語を使います。テンプレート文パーサ(構文解析)はテンプレート式パーサとは異なっており、特に基本代入 (=) とチェイン式 ( ; )に対応しています。

しかしながら一部のDartの構文は許されていません:



文のコンテキスト (Statement context)

式の場合と同様、文はその文のコンテキスト内にあるもの—一般的にはそのイベントをバインディングしている先のコンポネントのインスタンスcomponent instance--のみ参照できます。

テンプレート文はそのクラスの静的プロパティたち、あるいはdart:htmlからのwindowまたはdocumentのようなトップ・レベルの変数または関数を参照できません。またprintあるいはdart:mathからインポートした関数たちを直接呼び出せません。

(click)="onSave()"のなかのonSaveは必ずデータ・バインドされたコンポネントのインスタンスのメソッドでなければなりません。

文コンテキストはそのコンポネント以外のオブジェクトが含まれ得ます。テンプレート参照変数(template reference variable)はそのような代替コンテキスト・オブジェクトのひとつです。我々はイベント・バインディング文のなかで頻繁に予約語の$eventシンボルを見ますが、これは生起されたイベントの”メッセージ” ("message") または"ペイロード” ("payload") を意味します。



文のガイドライン (Statement guidelines)

式の場合と同様に、複雑なテンプレート文を書くのは避けてください。メソッド呼び出しまたはプロパティ代入が一般的であるべきです。

テンプレート式とテンプレート文が掴めたので、内挿の先の多種のデータ・バインディングの構文について学ぶこととします。



バインディング構文 (Binding syntax) :概要

データ・バインディングはアプリケーション・データ値で何をユーザが見るかを調和させるメカニズムです。HTMLに値たちをプッシュし、またHTMLから値たちをプルできる一方で、もしこれらの仕事をバインディングのフレームワークに委譲すれば、このアプリケーションは読み、書き、及び保守がより簡単になります。我々は単にバインディングの元とターゲットのHTML間のバインディングを宣言し、フレームワークにその仕事をさせれば良いのです。

Angularは多種のデータ・バインディングを用意していて、本章ではそれらの各々を説明してゆきます。最初に我々はAngularデータ・バインディングとその構文を高いレベルで眺めてみることにします。

我々はそのデータの流れの方向によって総てのバインディングを3つのカテゴリにグループ分けできます。各カテゴリはその固有の構文を持ちます:

データの方向

構文

バインディングのタイプ

片方向

データ・ソースからビュー・ターゲットへ

{{}}

[ターゲット] = ""

bind- ターゲット = ""

内挿

プロパティ

属性

クラス

スタイル

片方向

ビュー・ターゲットからデータ・ソースへ

(ターゲット) = ""

on- ターゲット = ""

イベント

双方向

[(ターゲット)] = ""

bindon- ターゲット = ""

双方向

内挿以外のバインディングのタイプでは等号のサインの左側に、句読点表記([], ())で囲まれたかたち、またはプレフィックス(bind-, on-, bindon-)が先行するかたちのいずれかで、ターゲット名が付されます。

ターゲットとは何でしょうか?その質問に答える前に、我々は新しい方法でテンプレートHTMLを見るということに挑戦しなければなりません。



新しいメンタル・モデル

訳者注:オンラインの説明はここです。

データ・バインディングの能力を知ったこと、及びカスタムのマークアップでHTML語彙集を拡張できるようになったことから、テンプレートHTMLHTML Plusとして考えたくなりませんか?

確かにHTML Plusですが、これはまた我々が馴染んできたHTMLとは大きく異なっています。我々は新しいメンタル・モデルが必要なのです。

HTML開発の通常の過程では、我々はHTMLエレメントたちでビジュアルな構造をつくり、文字列常数を使って要素の属性たちを設定することでこれらの要素たちを加工しています。

<div class="special">Mental Model</div>
<img src="assets/images/hero.png">
<button disabled>Save</button>

Angularテンプレートのなかでは我々はそれでもこのようにストラクチャを作り属性値を初期化します。

次に我々はHTMLをカプセル化するコンポネントたちで新しい要素たちを作り、それらをあたかも生来のHTML要素だったかの如くテンプレートの中に落すことを学びます。以下のコードの<hero-detail>という新しい要素がそうです。

<!-- Normal HTML -->
<div class="special">Mental Model</div>
<!-- Wow! A new element! -->
<hero-detail></hero-detail>

これこそ HTML Plusです。

データ・バインディングに関し学習を始めます。我々が出会う最初のバインディングは次のようなものでしょう:

<!-- Bind button disabled state to `isUnchanged` property -->
<button [disabled]="isUnchanged">Save</button>

我々はすぐにこの妙な括弧の表記法に気が付きます。その先では我々がそのボタンのdisabled属性にバインディングしようとしており、その属性をそのコンポネントのisUnchangedプロパティの現在値でセットしようとしていると我々の直感が知らせています。

その直感は間違っています!我々の毎日のHTMLのメンタル・モデルは我々を誤解させています。実際、我々が一旦データ・バインディングを開始させたら、我々はもはやHTML属性たちで作業をしていません。我々は属性たちをセットしていません。我々はDOM要素たち、コンポネントたち、及びディレクティブたちのプロパティたちをセットしているのです。

HTML属性たち対DOMプロパティたち

訳者注:オンラインの説明はここです。

HTML属性たちとDOMプロパティたちの相違は如何にAngularのバインディングが機能しているかを理解するうえで決定的です。

属性はHTMLで定義されています。プロパティはDOM (Document Object Model)で定義されています。

  • 少数のHTML属性はプロパティへの11マッピングがあります。id が一つの例です。

  • 一部のHTML属性はそれに対応するプロパティがありません。colspanが一つの例です。

  • 一部のDOMプロパティは対応する属性を持ちません。textContentが一つの例です。

  • 多くのHTML属性はプロパティへのマップがあるかに見えます....しかしそれは我々の思考の邪魔です!

最後のカテゴリが特に混乱させ得ます...一般則を理解するまでは:

属性はDOMプロパティを初期化し、それで終了します。プロパティの値は変更できます;属性値は変更できません。

例えば、ブラウザが<input type="text" value="Bob">を描写するとき、ブラウザは"Bob"で初期化された値の属性で対応するDOMノードを生成します。

ユーザが"Sally"とその入力ボックスに入力したとき、DOM要素のvalueプロパティは"Sally"となります。しかしHTMLvalue属性は変化せず、これはその入力要素にその属性を聞けば判ります:input.getAttribute('value') // returns "Bob"

HTML属性値は初期値を指定しています;DOMvalueプロパティは現行値です。

disabled属性はもうひとつの妙な例です。ボタンのdisabledプロパティはデフォルトではfalseなのでそのボタンはenabledになります。disabled属性を付加すると、その存在だけがそのボタンのdisabledプロパティをtrueするので、そのボタンはdisabledとなります。

disabled属性を付加したり削除したりするとそのボタンはdisabledあるいはenabled状態になります。この属性の値は意味がなく、だから我々は<button disabled="false">Still Disabled</button>と書いてenableにできないのです。

このボタンのdisabledプロパティ(Angularのプロパティ・バインディングのように)をセットすればこのボタンをdisabledあるいはenabled状態にできます。

HTML属性とDOMプロパティは同じものではありません、例え同じ名前を持っていたとしても。

これは非常に重要なことなので、繰り返します。

テンプレート・バインディングはプロパティとイベントで動作し、属性では動作していません。

属性なしの世界

Angularの世界では、属性の唯一の役割は要素とディレクティブの状態の初期化です。データをバインドすると、我々はもっぱら要素とディレクティブのプロパティたちとイベントたちを扱います。属性は実行的に消えてしまいます。

このモデルをしっかり念頭に置いて、バインディング・ターゲットを学びましょう。



バインディングのターゲット (Binding targets)

データ・バインディングのターゲットDOMのなかの何かです。バインディングのタイプによって、ターゲットは(要素 | コンポネント | ディレクティブ)のプロパティ、(要素 | コンポネント | ディレクティブ)のイベント、または(滅多にはありませんが)属性名であり得ます。以下の表がそれらをまとめています。

バインディングのタイプ

ターゲット

プロパティ

要素プロパティ

コンポネント・プロパティ

ディレクティブ・プロパティ

<img [src] = "heroImageUrl">
<hero-detail [hero]="currentHero"></hero-detail>
<div [ngClass] = "{selected: isSelected}"></div>

イベント

要素イベント

コンポネント・イベント

ディレクティブ・イベント

<button (click) = "onSave()">Save</button>
<hero-detail (deleteRequest)="deleteHero()"></hero-detail>
<div (myClick)="clicked=$event">click me</div>

双方向

イベントとプロパティ

<input [(ngModel)]="heroName">

属性

属性(例外)

<button [attr.aria-label]="help">help</button>

class

classプロパティ

<div [class.special]="isSpecial">Special</div>

style

styleプロパティ

<button [style.color] = "isSpecial ? 'red' : 'green'">

専門用語の雲から降りてこれらのバインディングのタイプの各々を具体的な詳細にわたってみてみましょう。



プロパティ・バインディング (Propaty bindings)

訳者注:オンラインのデモはここです。

あるビューの要素のプロパティをテンプレート式の値でセットしたいときは、我々はテンプレート・プロパティ・バインディングを書きます。

最も一般的なプロパティ・バインディングは、コンポネントのプロパティ値ある要素のプロパティセットするものです。例としてはあるイメージ要素のsrcプロパティをあるコンポネントのheroImageUrlにバインドします:

<img [src]="heroImageUrl">

別の例ではそのコンポネントがそれはisUnchangedといったときにそのボタンをdisabledにします:

<button [disabled]="isUnchanged">Cancel is disabled</button>

別の例ではディレクティブのプロパティをセットしています:

<div [ngClass]="classes">[ngClass] binding to the classes property</div>

更に別の例としては、カスタム・コンポネントのモデル・プロパティをセットしています(親と子供のコンポネント間の通信のためのすごい手段です)。

<hero-detail [hero]="currentHero"></hero-detail>



一方通行 (One-way in)

人々はしばしばプロパティ・バインディングのことを、コンポネントのデータ・プロパティからターゲット要素のプロパティへの一方向にデータがながれるので、一方通行のデータ・バインディングと言います。

我々はプロパティ・バインディングをターゲット要素から値を引き出す為に使うことはできません。我々はターゲット要素のプロパティにそれを読み出すためにバインドできません。セットしかできません。

プロパティ・バインディングをターゲット要素上のメソッドを呼び出すために使うこともできません。

もしその要素がイベントを生起させるものなら、それはイベント・バインディングで聴くことができます。

もしターゲット要素のプロパティを読み出す、またはそのメソッドたちの一つを呼ばねばならないときは、我々は別の技術が必要でしょう。ViewChild ContentChild に関してはAPI参照を見てください。



バインドのターゲット (Binding target)

各括弧で囲まれたなかの要素のプロパティはターゲットのプロパティを特定するものです。以下のコードの中のターゲット・プロパティはイメージ要素のsrcプロパティです。

<img [src]="heroImageUrl">

人によっては代替のカノニカル書式 (canonical form) として知られるbind-プレフィックスのほうを使っています:

<img bind-src="heroImageUrl">

ターゲット名は、例えどこか他の名前だとして出てきているとしても、常にプロパティの名前です。我々はsrcを見、これは属性の名前だと考えるかもしれません。違います。これはイメージ要素プロパティの名前です。

要素のプロパティたちはより一般的なターゲットであるかもしれませんが、以下の例で示すように、Angularはその名前が既知のディレクティブのプロパティかどうかを最初に調べます(訳者中:これは[ngClass] binding to the classes propertyと表示されます):

<div [ngClass]="classes">[ngClass] binding to the classes property</div>



技術的には、Angularはその名前を、ディレクティブ入力、そのディレクティブの入力配列にリストされたプロパティ名のひとつ、または@Input()で修飾されたプロパティとの一致を調べます。そのような入力はそのディレクティブ自身のプロパティにマッピングしています。

もし既知のディレクティブまたは要素との一致検出にその名前が失敗したら、Angularは「未知のディレクティブ (unknown directive)」を報告します。



副作用を避ける

既に議論してきたように、テンプレート式の計算は可視の副作用があってはなりません。式の言語自身我々を安全に保つ役割を持っています。我々は値をプロパティ・バインディング式のなかの何かに代入できないし、増分または減分演算子を使うこともできません。

無論、我々の式は副作用をもつプロパティまたはメソッドを呼び出す可能性はあります。Angularはそれを知る手段も我々を止めさせる手段も持っていません。

式はなにかgetFoo()のようなものを呼び出し得ます。我々が知っているのはgetFoo()が何をするかということです。もしgetFoo()が何かを変え、我々がたまたまその何かにバインディングしているとすると、我々には好ましくない経験をするリスクがあります。Angularはその変化した値を表示するかもしれないし表示しないかもしれません。Angularはその変更を検出し警告エラーをスローするかもしれません。我々の一般的な助言は:値を返すデータのプロパティたちとメソッドたちに留まり、それ以上のことはしない」ということです。



適切な型を返す

テンプレート式はターゲットのプロパティによって期待されている型の値を計算すべきです。ターゲットのプロパティが文字列を期待しているときは文字列を返します。もしターゲットのプロパティが数を期待しているときは数を返します。もしターゲットのプロパティがオブジェクトを期待しているときはオブジェクトを返します。

HeroDetailコンポネントのheroプロパティはHeroオブジェクトを期待しており、これはまさしくプロパティ・バインディングで我々が送っているものです:

<hero-detail [hero]="currentHero"></hero-detail>



Dartの相違点:型の例外

チェックド・モードでは、もしテンプレート式が型をもたらし、ターゲット・プロパティの型が代入互換性がないときは、型例外がスローされます。チェックド・モードに関しては、Dart言語ツアーのなかの重要なコンセプトの箇所を見てください。



括弧を思い出してください

角括弧はAngularにそのテンプレート式を計算するよう告げます。もしこれを忘れると、Angularはこの文字列を常数とみなしその文字列でターゲットのプロパティを初期化します。その文字列を計算しません!

次のような間違いをしないでください:

<!-- ERROR: HeroDetailComponent.hero expects a
     Hero object, not the string "currentHero" -->
  <hero-detail hero="currentHero"></hero-detail>



Dartの相違点:型の例外の例

チェックド・モードでは、上のコードは型の例外を起こし、次のような報告を出します:String isn't a subtype of Hero.



一回の文字列の初期化 (One-time string initialization)

以下の総てがtrueのときは角括弧は外すべきです:

  • そのターゲット・プロパティが文字列の値を受け付けている

  • その文字列は固定した値で我々はそれをテンプレートに置ける

  • この初期値は決して変化しない

我々は標準のHTMLのなかで日常的に属性を初期化しており、ディレクティブとコンポネントのプロパティの初期化にはずばり機能します。以下の例は HeroDetailComponentprefixプロパティを固定した文字列(テンプレート式ではなく)で初期化しています。Angularはそれをセットし、それに関して忘れてしまいます。

<hero-detail prefix="You are my" [hero]="currentHero"></hero-detail>

一方[hero]のほうは該コンポネントのcurrentHeroプロパティへのライブのバインディングを維持します。



プロパティ・バインディングか内挿か?

しばしば内挿かプロパティ・バインディングかの選択肢を持つことがあります。以下のバインディングの対は同じことをします:

<p><img src="{{heroImageUrl}}"> is the <i>interpolated</i> image.</p>

<p><img [src]="heroImageUrl"> is the <i>property bound</i> image.</p>


<p><span>"{{title}}" is the <i>interpolated</i> title.</span></p>

<p>"<span [innerHTML]="title"></span>" is the <i>property bound</i> title.</p>

内挿は多くの場合プロパティ・バインディングの便利な代替手段となります。実際、Angularはこビューを描画する前にれらの内挿を対応するプロパティ・バインディングに翻訳します。

どっちが良いかの技術的な理由はありません。我々は読みやすさに傾斜する傾向があり、内挿が好まれがちです。我々はコーディング・スタイル規約を作り、その規約に適合し手にしたタスクにとって最も自然だと感じる書式を選択することを勧めます。



コンテントのセキュリティ

次のような悪意のあるコンテントを想像してください。

String evilTitle = 'Template <script>alert("evil never sleeps")</script>Syntax';

幸いにもAngularのデータ・バインディングは危険なHTMLにたいし警戒態勢をとっています。Angularはそれらを表示する前にその値たちを消毒します。Angularはスクリプト・タグを持ったHTMLが内挿によってであろうとプロパティ・バインディングによってであろうとブラウザに流入するのを許しません。

<p><span>"{{evilTitle}}" is the <i>interpolated</i> evil title.</span></p>

<p>"<span [innerHTML]="evilTitle"></span>" is the <i>property bound</i> evil title.</p>

内挿はプロパティ・バインディングとは別のアプローチでスクリプト・タグを処理しますが、双方のアプローチともにそのコンテントを無害な方法で描写します。




属性、クラス、及びスタイルのバインディング

テンプレート構文にはプロパティ・バインディングにはあまり適していないシナリオのための特別の一方向バインディングが用意されています。



属性バインディング (Attribute binding)

訳者注:オンラインのデモはここです。

我々は属性バインディングで属性の値を直接セットできます。

これはバインディングはターゲットのプロパティをセットするというルールに対する唯一の例外です。これは属性の生成とセットをする唯一のバインディングです。

我々は本章を通じてプロパティ・バインディングで要素のプロパティをセットすることが常に文字列で属性をセットするより好ましいものだと強調してきました。どうしてAngulerは属性バインディングをオファーしているのでしょうか?

我々はバインドする要素のプロパティがないときには属性バインディングを使わねばなりません。

ARIASVG、及び table span属性を考えて下さい。これらは純粋な属性です。これらには対応する要素プロパティがなく、これらは要素プロパティたちをセットしません。バインドするプロパティ・ターゲットが存在しないのです。

例えば次のようなものを書こうとすると、この事実が痛いほどわかります:

<tr><td colspan="{{1 + 1}}">Three-Four</td></tr>

次のようなエラーが表示されます:

Template parse errors:

Can't bind to 'colspan' since it isn't a known native property

このメッセージが言っているように、<td>要素はcolspanプロパティを持っていません。"colspan"属性は持っていますが、内挿とプロパティ・バインディングはプロパティのみをセットでき、属性はセットできません。

そのような属性を生成したりそれにバインドしたりするための属性バインディングが必要です。

属性バインディング構文はプロパティ・バインディングと似ています。波括弧間の要素プロパティの代わりに、attrプレフィックスで始まり、ドット (.) がつき、そしてその属性の名前が続きます。次に我々は文字列をもたらす式を使って属性値をセットします。

以下は[attr.colspan]を計算した値にバインドしています:

<table border=1>
  <!--  expression calculates colspan=2 -->
  <tr><td [attr.colspan]="1 + 1">One-Two</td></tr>

  <!-- ERROR: There is no `colspan` property to set!
    <tr><td colspan="{{1 + 1}}">Three-Four</td></tr>
  -->

  <tr><td>Five</td><td>Six</td></tr>
</table>

これは次のように描写されます:

One-Two

Five

Six

属性バインディングの最も多い使用例は次の例のようにARIA属性のセットです:

<!-- create and set an aria attribute for assistive technology -->

<button [attr.aria-label]="actionName">{{actionName}} with Aria</button>



classバインディング (Class binding)

訳者注:オンラインのデモはここです。

我々はclassバインディングを使ってある要素のclass属性からCSS class名の付加と削除が可能です。

classバインディング構文はプロパティ・バインディングと似ています。角括弧間の要素プロパティの替わりに、まずプレフィックスclassで始まり、オプショナルにドット(.)CSS class[class.class-name]がつきます。

以下の例ではこのアプリケーションの"special"クラスをclassバインディングで追加と削除する方法を示しています。まずバインディングなしでの属性のセット(標準のclass属性のセット)を示します:

<!-- standard class attribute setting -->

<div class="bad curly special">Bad curly special</div>

望むclass名の文字列へのバインディングでこれを置き換えます;これはすべてか無の置き換えバインディング(バインディングで総てのclass名をリセット/オーバライドする)です。

<!-- reset/override all class names with a binding  -->
<div class="bad curly special"
     [class]="badCurly">Bad curly</div>

最後に、特別のclass名のバインドできます。Angularはこのテンプレート式がtrueと計算されたらこのclassを追加します。この式がfalseのときはこのclassを削除します(プロパティで"special”クラスをオン/オフさせる)。

<!-- toggle the "special" class on/off with a property -->
<div [class.special]="isSpecial">The class binding is special</div>

<!-- binding to `class.special` trumps the class attribute -->
<div class="special"
     [class.special]="!isSpecial">This one is not so special</div>



単一のclass名をオン/オフさせるのは問題ありませんが、一般的には同時に複数のclass名を管理するには NgClassディレクティブのほうが好まれます。



styleバインディング (Style binding)

訳者注:オンラインのデモはここです。

Styleバインディングでインラインでstyleたちをセットできます。

Styleバインディング構文はプロパティ・バインディングと似ています。角括弧間の要素プロパティの替わりに最初にプレフィックス styleが、以下ドット(.)CSSstyleプロパティが続きます:[style.style-property]

<button [style.color] = "isSpecial ? 'red': 'green'">Red</button>

<button [style.background-color]="canSave ? 'cyan': 'grey'" >Save</button>

訳者注:Dartの条件式(conditional expression)に慣れましょう。

一部のスタイル・バインディング・スタイルたちはunit拡張を持っています。ここでは条件付きでフォント・サイズを“em” と “%”単位でセットしています。

<button [style.font-size.em]="isSpecial ? 3 : 1" >Big</button>

<button [style.font-size.%]="!isSpecial ? 150 : 50" >Small</button>



単一のstyleをセットするにはこれは問題ありませんが、一般的には同時に複数のインライン・スタイルたちをセットするには NgStyleディレクティブのほうが好まれます。



styleプロパティ名は上で示したようなdash-case'-'で複合語を繋ぐ)またはfontSizeのようなcamelCase(キャメルケースは、複合語をひと綴りとして、要素語の最初を大文字で書き表すこと)で書けます。



Dartの相違点:スタイルのプロパティ名

Angular Dartではダッシュ・ケースとキャメル・ケースのstyleプロパティの名づけのスキームは同じですが、 dart:htmlCssStyleDeclarationメソッドたちのgetPropertyValue()setProperty()のメソッドはダッシュ・ケースの名前のみを認識します。従ってダッシュ・ケース・スタイル・プロパティ名のみを使うことを奨励します。



イベント・バインディング (Event binding)

訳者注:オンラインのデモはここです。

これまでのところ我々が見てきたバインディングは一方向、即ちコンポネントから要素へのデータの流れでした。

ユーザたちはそのスクリーンを単に凝視するだけではありません。彼らは入力ボックスにテキストを入力します。彼らはリストからアイテムを選択します。彼らはボタンをクリックします。そのようなユーザのアクションは反対方向、即ち要素からコンポネントへのデータの流れをもたらします。

ユーザのアクションを知る唯一の手段はキーストローク、マウスの移動、クリック、及びタッチのようなあるイベントを聴く(リスンする)ことです。我々はAngularのイベント・バインディングを介してユーザ・アクションたちのなかの我々の関心項目を宣言します。

イベント・バインディング構文はイコール・サインの左側に丸括弧で囲ったターゲット・イベント、右側に引用符で囲ったテンプレート文で構成されます。以下のイベント・バインディングではボタンのクリック・イベントを聴き、クリックが発生したときは何時でもそのコンポネントのonSave()メソッドを呼びます:

<button (click)="onSave()">Save</button>



ターゲット・イベント (Target event)

丸括弧間の名前 — 例えば(click) — はターゲットのイベントを特定します。以下の例では、ターゲットはボタンのクリック・イベントです。

<button (click)="onSave()">Save</button>

一部の人たちはカノニカル書式として知られるon- プレフィックスのほうを好んで使用しています:

<button on-click="onSave()">On Save</button>

要素のイベントはより一般的なイベントのターゲットかもしれませんが、以下の例で示すように、Angularは最初にその名前が既知のディレクティブのイベント・プロパティと合致するかを調べます(myClickはカスタムのMyClickDirective上のイベントです):

<!-- `myClick` is an event on the custom `MyClickDirective` -->

<div (myClick)="clickMessage=$event">click with myClick</div>



myClickディレクティブはinput/outputプロパティをエイリアス化するの節で更に説明します。

もしその名前がエレメントのイベントまたは既知のディレクティブの outputプロパティの名前との一致がとれないときは、Angularは“unknown directive”エラーを報告します。



$eventとイベント処理文

イベント・バインディングでは、Angularは該ターゲット・イベントのためのイベント・ハンドラを用意します。

そのイベントが生起されたら、このハンドラはテンプレート文を実行します。このテンプレート文は一般的にはレシーバ (receiver) が関与します。このレシーバはそのイベントに応答してあるアクション — 例えばHTMLコントロールからの値をモデルにストアする — を実行します。

このバインディングはそのイベントに関する情報(データの値たちが含まれる)を$eventという名前のイベント・オブジェクトを介して伝達します。

このイベント・オブジェクトの形はターゲット・イベントによって決まります。もしそのターゲット・イベントがネーティブのDOM要素イベントの時は、$eventDOMのイベント・オブジェクトとなり、targettarget.valueといったプロパティを持ちます。

次の例を考えてください:

<input [value]="currentHero.firstName"
       (input)="currentHero.firstName=$event.target.value" >

このコードは入力ボックスのvalueプロパティをfirstNameプロパティへのバインディングでセットしています。このvalueの変化を聴くために、このコードはこの入力ボックスのinputイベントにバインドしています。ユーザが変更させたら、inputイベントが生起され、このバインディングはDOMイベント・オブジェクトの$eventを含むコンテキストの中でこの文を実行します。

firstNameプロパティを更新させるには、この変更されたテキストは$event.target.valueのパスに従って取り出されます。

もし該イベントがあるディレクティブ(コンポネントはディレクティブであることを思い出してください)に属するときは、$eventは該ディレクティブが作り出すと決める形を持つことになります。



EventEmitterによるカスタムのイベント (Custom events with EventEmitter)

ディレクティブは一般にAngularEventEmitterを用いてカスタムのイベントを生起します。このディレクティブはあるEventEmitterを生成し、そのプロパティを外部からアクセスできるようにします。このディレクティブはイベントをファイアするのにEventEmitter.emit(payload)を呼び、メッセージ・ペイロードに渡し、これが何にでもなり得ます。親のディレクティブはこのプロパティにバインドし、$eventオブジェクトを介してこのペイロードにアクセスすることでこのイベントを聴きます。

heroの情報を提示しユーザのアクションに応答するHeroDetailComponentを考えてみましょう。このHeroDetailComponentdeleteボタンを有していますが、hero自身をどう削除するかは知りません。それができるベストなことは該ユーザの削除リクエストを報告するイベントを生起することです。

以下はこの HeroDetailComponentからの関連個所の抜き出しです:

lib/hero_detail_component.dart(template)

template: '''
  <div>
    <img src="{{heroImageUrl}}">
    <span [style.text-decoration]="lineThrough">
      {{prefix}} {{hero?.fullName}}
    </span>
    <button (click)="delete()">Delete</button>
  </div>''')



lib/hero_detail_component.dart(deletRequest)

// このコンポネントは要求ができるが実際にheroを削除することはできない

final deleteRequest = new EventEmitter<Hero>();
void delete() {
  deleteRequest.emit(hero);
  lineThrough = (lineThrough == '') ? 'line-through' : '';
}

このコンポネントはEventEmitterを返すdeleteRequestプロパティを定義しています。ユーザがdeleteをクリックすると、このコンポネントはdelete()メソッドを呼び出し、このEventEmitterheroオブジェクトをエミット(放出)するように告げます。

これで HeroDetailComponentdeleteRequestイベントにバインドしているホストの親のコンポネントのことを想像してください。

<hero-detail (deleteRequest)="deleteHero($event)" [hero]="currentHero"></hero-detail>

このdeleteRequestイベントがファイアしたら、Angularは親のコンポネントのdeleteHeroメソッドを呼び、$event変数の中のhero-to-deleteHeroDetailがエミットした)を渡します。



テンプレート文には副作用があります

deleteHeroメソッドは副作用があります:これはheroを削除します。テンプレート文の副作用は単にOKではなく、予想されているものです。

heroを削除するとこのモデルを更新し、多分クエリたちを含む他の変更のトリガとなり、リモート・サーバを救済します。これらの変更はシステムを通して浸透し、最終的にはこのあるいは他のビューのなかで表示されます。



双方向バインディング (Two-way binding)

我々はしばしばユーザが変更を加えたときにデータ・プロパティの表示とそのプロパティの更新の双方をさせたいと考えます。

要素のサイドではそれは特定の要素のプロパティをセットすることと、要素の変化のイベントを聴くことの組み合わせということになります。

Angularはこの目的のための特別の双方向データ・バインディング構文の[(x)]を用意しています。この[(x)]構文は各括弧によるプロパティ・バインディングの[x]と丸かっこによるイベント・バインディングの(x)を組み合わせたものです。

[( )] = 箱の中のバナナ

箱の中のバナナとビジュアルに覚えていただければ、「各括弧の中は丸括弧」ということを忘れないと思います。

[(x)]構文は、その要素がセット可能なxと呼ばれるプロパティを持っており、対応する xChangeという名前のイベントを持っているときに説明しやすくなります。以下はこのパタンにフィットした SizerComponentです。これはsizeという値のプロパティとそれに伴う sizeChangeというイベントを持っています:

lib/sizer_component.dart

import 'dart:math';
import 'package:angular2/core.dart';
@Component(
    selector: 'my-sizer',
    template: '''
      <div>
        <button (click)="dec()" title="smaller">-</button>
        <button (click)="inc()" title="bigger">+</button>
        <label [style.font-size.px]="size">FontSize: {{size}}px</label>
      </div>''')
class MySizerComponent {
  static final defaultPxSize = 14;
  @Input()
  String size;
  @Output()
  var sizeChange = new EventEmitter<String>();
  void dec() => resize(-1);
  void inc() => resize(1);
  void resize(num delta) {
    final numSize = num.parse(size, (_) => defaultPxSize);
    size = min(40, max(8, numSize + delta)).toString();
    sizeChange.emit(size);
  }
}

初期のsizeはプロパティ・バインディングからの入力値です。+-のボタンをクリックするとこのサイズはmin/max値の制約の範囲で増加または減少し、次にその調整されたサイズでsizeChangeイベントを生起(エミット)します。

以下はAppComponent.fontSizePxSizerComponentに双方向バインドされているサンプルです:

<my-sizer [(size)]="fontSizePx"></my-sizer>

<div [style.font-size.px]="fontSizePx">Resizable Text</div>

AppComponent.fontSizePxは初期SizerComponent.sizeの値を確立します。ボタンをクリックすると双方向バインディングを介してAppComponent.fontSizePxを更新します。変更されたAppComponent.fontSizePxの値はstyleバインディング経由で流れ、表示されるテキストを大きくまたは小さくします。ライブのデモTwo-way Bindingのところで試してください。

この双方向バインディングは実際単にプロパティ・バインディングとイベント・バインディングの糖衣構文(シンタックス・シュガー)です。AngularSizerComponentを次のように糖衣をはがします:

<my-sizer [size]="fontSizePx" (sizeChange)="fontSizePx=$event"></my-sizer>

$event変数はSizerComponent.sizeChangeイベントのペイロード(payload)を含んでいます。Angularはユーザがボタンをクリックしたときに$event値を AppComponent.fontSizePxに代入します。

明らかに双方向バインディング構文は分離したプロパティとイベントのバインディングに比べれば非常に便利なものです。

我々は <input><select>のようなHTML form要素で双方向バインディングを使いたいのです。残念ながらネーティブなHTML要素にはx値とxChangeイベントのパタンに従ったものはありません。

幸い、Angular NgModelディレクティブform要素たちへの双方向バインディングを可能とする橋渡しになっています。



NgModelによる双方向バインディング

データ入力のフォームを開発する際に、我々はしばしばユーザが変更を加えたときにデータ・プロパティの表示とそのプロパティの更新の双方をさせたいと考えます。

NgModelディレクティブを使った双方向バインディングはそれを簡単にします。以下はその例です:

<input [(ngModel)]="currentHero.firstName">



[(ngModel)]の内側

firstNameのバインディングを振り返ってみると、我々は<input>要素のvalueプロパティへのバインディングとinputイベントへのバインドとバインディングを分けても同じ結果が得られた、ということに言及することは重要です。

<input [value]="currentHero.firstName"
       (input)="currentHero.firstName=$event.target.value" >

これは面倒くさい方法です。どの要素プロパティがセットしどの要素イベントがユーザの変更をエミットするかを誰が思い出せますか?どうやって入力ボックスから現在表示されているテキストを抽出しデータ・プロパティを更新できるようにするのでしょうか?誰が毎回それを調べたいと思いますか?

この ngModelディレクティブはこれらの面倒な詳細をそれ自身のngModel入力プロパティとngModelChange出力プロパティで隠してくれます。

<input
  [ngModel]="currentHero.firstName"
  (ngModelChange)="currentHero.firstName=$event">



ngModelデータ・プロパティはその要素のvalueプロパティをセットし、ngModelChangeイベント・プロパティはこの要素のvalueへの変更をリスンします。

詳細は要素の各種類によって決まるので、従ってNgModelディレクティブは入力テキスト・ボックスのようなControlValueAccessorによってサポートされている特定のform要素に対してのみ機能します。

適切な値アクセサ (value accessor)を書かない限りカスタムのコンポネントに[(ngModel)]を適用できません。これは本章の範囲を超えた技術です。これはAngularのコンポネントまたはWebComponentでやって欲しい類のものですが、そのAPIを我々はコントロールできません。

我々がコントロールするAngularのコンポネントに対してはそれは全く不必要です....何故なら我々は値とイベントのプロパティたちに対しAngularのベーシックな双方向バインディング構文に合致した名前を付すことができNgModelをともにスキップできるからです。

ngModelバインディングたちを分離させるのはその要素のネーティブなプロパティたちへのバインディングよりも確かに進歩しています。

我々はデータ・プロパティを2回書くべきではありませんでした。Angularは単一の宣言でそのコンポネントのデータ・プロパティを捕捉しまたそれをセットできるべきだったのです — それが [(ngModel)] 構文で可能になっているのです:

<input [(ngModel)]="currentHero.firstName">

[(ngModel)]だけで良いのでしょうか?拡張した書式に後退するような理由はないのでしょうか?

[(ngModel)]構文はデータ・バインドされたプロパティのみセットできます。もしそれ以上のなにかあるいは別の何かが必要になったら、自ら拡張した書式を書く必要があります。

入力した値を大文字にしてしまうようなくだらぬものを試してみましょう:

<input
  [ngModel]="currentHero.firstName"
  (ngModelChange)="setUpperCaseFirstName($event)">

以下はアクションのバリエーションの総てで、大文字化のバージョンも一番下に含まれています:


訳者注:オンラインのデモはここです。

組み込みディレクティブ (Built-in directives)

Angularの初期のバージョンでは70を超える組み込みディレクティブがありました。コミュニティがさらに多くのディレクティブに貢献し、数えきれないプライベートなディレクティブたちが内部アプリケーションのために作られています。

我々はAngularのなかのこれらのディレクティブの多くは必要としません。かなり頻繁に我々はもっと有能で表現力あるAngularのバインディング・システムで同じ結果を達成できます。どうして我々は以下のような単純なバインディングで書けるのにクリックを扱うディレクティブを作るのでしょうか?

<button (click)="onSave()">Save</button>

我々はそれでも複雑なタスクを簡素化するディレクティブたちの恩恵を受けています。Angularはそれでも組み込みディレクティブたちが同梱されています;でもそんなに多くはありません。自分自身のディレクティブを書くこともありますが、そんなに多くを書くことはありません。

この節では最も頻繁に使われる組み込みディレクトリの幾つかを見てみます。

訳者注:組み込みディレクティブには”ng”が頭についていますが、これはstackoverflowによれば

  1. "Angular"の略で、発音が似ているから(広東語ではNgという苗字は"Ang"に似た発音になる?という説まである)

  2. Next Generationの略でAngularが次世代のHTMLだから

などの説があります。自分で作成したディレクティブには絶対これを付けないでください。



NgClass

一般的に我々はCSSクラスたちを動的に付加または削除することでどう要素たちが画面に現れるかを制御しています。我々はNgClassをバインドすることで幾つかのクラスを同時に付加または削除できます。

クラス・バインディングは単一のクラスを追加または削除するには良い手段です。

<!-- toggle the "special" class on/off with a property -->

<div [class.special]="isSpecial">The class binding is special</div>

多くのCSSクラスたちを同時に付加または削除したいときは、NgClassディレクティブがベター・チョイスかもしれません。

NgClassを適用する良い手段はそれを key:valueコントロールMapにバインドすることです。このオブジェクトの各キーはCSSクラス名です;もしこのクラスが付加されるべきものならその値はtrue、削除すべきものはfalseです。

3つのCSSクラスの状態を管理する setClassesといったコンポネント・メソッドを考えてみましょう:

  Map<String, bool> setClasses() {
    final classes = {
      'saveable': canSave, // true
      'modified': !isUnchanged, // false
      'special': isSpecial // true
    };
    // compensate for DevMode (sigh)
    if (JSON.encode(_previousClasses) == JSON.encode(classes))
      return _previousClasses;
    _previousClasses = classes;
    return classes;
  }

これでsetClassesを呼びこれに従いこの要素のclassesをセットするNgClassプロパティ・バインディングを付加できます:

<div [ngClass]="setClasses()">This div is saveable and special</div>

訳者注:オンラインのデモはここです。



NgStyle

そのコンポネントの状態に基づいてインラインのスタイルを動的にセットできます。 NgStyleへのバインディングを使うと同時に多くのインラインのスタイルたちを同時にセットできます。

スタイル・バインディングは単一のスタイル値をセットするのは簡単な手段です。

<div [style.font-size]="isSpecial ? 'x-large' : 'smaller'" >
  This div is x-large.
</div>

多くのインラインのスタイルを同時にセットしたいときは NgStyleディレクティブがベター・チョイスかもしれません。

NgStyleを適用するにはそれを key:valueコントロールMapにバインドすることです。このオブジェクトの各キーはスタイル名です;その値はそのスタイルに適切なものであれば構いません。

3つのスタイルを定義しているオブジェクトを返すsetStylesのようなコンポネント・メソッドを考えてみましょう:

Map<String, String> setStyles() {
  return <String, String>{
    'font-style': canSave ? 'italic' : 'normal', // italic
    'font-weight': !isUnchanged ? 'bold' : 'normal', // normal
    'font-size': isSpecial ? '24px' : '8px' // 24px
  };
}

これでsetStylesを呼びこの要素のスタイルをそれに応じセットするNgStyleプロパティ・バインディングを付加できます:

<div [ngStyle]="setStyles()">
  This div is italic, normal weight, and extra large (24px).
</div>

訳者注:オンラインのデモはここです。



NgIf

NgIfディレクティブをtrue式にバインディングすることでDOMに対しある要素の副ツリー( subtree:要素とその子供たち)を付加できます。

<div *ngIf="currentHero != null">Hello, {{currentHero.firstName}}</div>



ngIfの前に付くアスタリスク (*) 忘れないでください。更なる情報は* <template>の節を見てください。

false式にバインドするとこのDOMからこの要素の副ツリーを削除します。

<!-- because of the ngIf guard
    `nullHero.firstName` never has a chance to fail -->
<div *ngIf="nullHero != null">Hello, {{nullHero.firstName}}</div>

<!-- Hero Detail is not in the DOM because isActive is false-->
<hero-detail *ngIf="isActive"></hero-detail>



Dartの相違点:TRUTHY/FALSEYという値はありません

チェックド・モードにおいてはDartBooleanの値(型boolのもの)はtrueまたはfalseであることを予期しています。プロダクション・モードにおいてさも、Darttrueと扱うものはtrueの値です;他の総ての値はfalseです。一方TypeScriptJavaScriptでは多くの値(非nullのオブジェクトたちを含む)をtrueとして扱います。例えば、 TypeScript Angularのプログラムは*ngIf="currentHero" といったコードがありますが、Dartのプログラムでは*ngIf="currentHero != null"という記述になります。

TypeScriptのコードをDartコードに変換するときは、 true/false問題に注意してください。例えば、!= nullを忘れるとチェックド・モードでは例外 – 例えば"EXCEPTION: type 'Hero' is not a subtype of type 'bool' of 'boolean expression'" – が起きます。更なる情報は Dart language tourBooleansを見てください。

訳者注:オンラインのデモはここです。



可視性(Visibility)NgIfは同じではない

ある要素の副ツリー(要素とその子供たち)をクラスまたはスタイルのバインディングで見せたり隠したりできます:

<!-- isSpecial is true -->

<div [class.hidden]="!isSpecial">Show with class</div>

<div [class.hidden]="isSpecial">Hide with class</div>


<!-- HeroDetail is in the DOM but hidden -->

<hero-detail [class.hidden]="isSpecial"></hero-detail>


<div [style.display]="isSpecial ? 'block' : 'none'">Show with style</div>

<div [style.display]="isSpecial ? 'none' : 'block'">Hide with style</div>

副ツリーを隠すことはNgIfで副ツリーを除外することとは全く異なります。

我々がそのエレメントの副ツリーを隠すときは、それはDOMに留まっています。この副ツリーのコンポネントたちは、それらの状態を含めて保存されます。Angularはたとえ不可視のプロパティであっても変化をチェックし続けます。この副ツリーはかなりのメモリと計算リソースを使います。

NgIffalseのときは、Angularは物理的にこの要素の副ツリーをDOMから外します。これは副ツリー内のコンポネントたちを、その状態を含めて破壊するので、潜在的に大幅のリソースを開放し、ユーザにはより良い性能をもたらします。

このshow/hide技術は多分小さな要素ツリーの時は問題ありません。ただし大きなツリーを隠すときは心配すべきで、NgIfのほうが多分ベター・チョイスです。いつも結論に飛び付く前に優劣を調べてください。



NgSwitch

我々は何らかの条件に基づいて可能な要素ツリーたちのセットからひとつの要素ツリー(エレメントとその子供たち)を表示したいときは、NgSwitchにバインドします。Angularは選択された要素ツリーのみをDOMに置きます。

以下はその例です:

<span [ngSwitch]="toeChoice">
  <span *ngSwitchWhen="'Eenie'">Eenie</span>
  <span *ngSwitchWhen="'Meanie'">Meanie</span>
  <span *ngSwitchWhen="'Miney'">Miney</span>
  <span *ngSwitchWhen="'Moe'">Moe</span>
  <span *ngSwitchDefault>other</span>
</span>

訳者注:オンラインのデモはここです。

我々はswitch値を返す式に親の NgSwitchディレクティブをバインドしています。この例ではこのvalueは文字列ですが、これは任意の型の値でもあり得ます。

この例では、親のngSwitchディレクティブは子供の<span>要素たちのセットをコントロールします。<span>match valueに固定されているかまたはdefaultとマークされているかです。

どの瞬間においても、これらのspanたちの最大ひとつはDOMの中にあります。

もしこのspanの合致する値switchと等しいときは、Angularはこの<span>DOMに付加します。spanのどれもが合致しないときは、AngularはデフォルトのspanDOMに付加します。Angularはその他の総てのspanを削除し破壊します。

この例ではこのspanのどの要素も置き換え可能です。その要素はそれ自身の要素に多量の副ツリーを持った<div>もあり得ます。この合致する<div>とその副ツリーのみがこのDOMに出現し、他は削除されます。

共聴する3つのディレクティブたちがここでは機能しています:

  1. ngSwitch: switch値を返す式にバインドされている

  2. ngSwitchCase: match値を返す式にバインドされている

  3. ngSwitchDefault: デフォルト要素上のマーカー属性a marker attribute on the default element

ngSwitchの前にアスタリスク(*)付けないでください。その代わりにプロパティ・バインディングを使ってください。

ngSwitchCasengSwitchDefaultの前にアスタリスク(*)を付けないでください。詳細は次節の「*とテンプレート」を見てください。



NgFor

NgForはリピータ・ディレクティブ — データ表示をカスタマイズする手段の一つ — です。

我々の目標はアイテムたちのリストの表示です。我々は如何に単一のアイテムが表示されるべきかを定義したHTMLのブロックを定義します。次にAngularに対しこのリストの各アイテムを描写するためのテンプレートとしてこのブロックを使うよう伝えます。

以下は簡単な<div>NgForを適用した例です:

<div *ngFor="let hero of heroes">{{hero.fullName}}</div>

以下の例のように我々はコンポネント要素に対しNgForを適用することもできます:

<hero-detail *ngFor="let hero of heroes" [hero]="hero"></hero-detail>



ngForの前にアスタリスク(*)を付けるのを忘れないでください。詳細は次節の「*とテンプレート」を見てください。

*ngForに代入されているテキストはこのリピータ過程を導く指示です。

訳者注:オンラインのサンプルも参考になります。



NgFor マイクロシンタックス

*ngForに代入されている文字列はテンプレート式ではありません。これはマイクロシンタックスで、Angularが解釈するそれ用の小さな言語です。この例では文字列"let hero of heroes"は以下のことを意味します:

heroesリストの中から各heroを取り出し、それをローカルのhero変数にストアし、それを各繰り返しのためにテンプレートされたHTMLが使えるようにする。

Angularはこの指示を新しい要素たちとバインディングたちのセットに翻訳します。

前の2つの例では ngForディレクティブは親のコンポネントのheroesプロパティで返されたheroesリスト上で繰り返し操作を行い、それが適用されている要素のインスタンスたちを表示します。Angularはこの配列のなかの各heroに対しこのテンプレートの新鮮なインスタンスを生成します。

heroの前のletというキーワードはheroと呼ばれるテンプレート入力変数を作ります。

テンプレート入力変数(template input variable)テンプレート参照変数(template reference variable)と同じではありません!

我々はこの変数を内挿でやっているようにこのテンプレートの中でheroのプロパティにアクセスするために使います。我々はまたhero-detailでやっているようにコンポネント要素へのバインディングの中にこの変数を渡せます。



インデックスつきNgFor (NgFor with index)

ngForディレクティブは各繰り返し操作のための0からその配列の長さまで増加するオプショナルのindexをサポートしています。我々はテンプレート入力変数のなかでこのindexを捕捉し、それを我々のテンプレートの中で使うことができます。

次の例はこのindexiという名前の変数で捕捉し、それを"1 - Hercules Son of Zeus"のように行を表示するのに使っています。

<div *ngFor="let hero of heroes; let i=index">{{i + 1}} - {{hero.fullName}}</div>



他の特別なローカル変数 – lasteven、及びoddといった値 - に関してはNgFor API 参照を見てください。



NgForTrackBy

特に大規模なリストの場合ngForディレクティブは潜在的に能力が落ちます。一つのアイテムへのわずかな変更、アイテムの削除、またはアイテムの追加がDOM操作のカスケードを引き起こします。

例えば、サーバに再クエリ要求をすることでheroesのリストをリフレッシュできます。リフレッシュされたリストはこれまで表示されていたheroesの殆ど(総てではないにしても)を多分含んでいます。

heroidが変わっていないのでこれが判ります。しかしAngularは新しいオブジェクト参照たちの新鮮リストのみを見ます。従って古いリストを引きはがし、これらのDOM要素たちを破棄し、新しいDOM要素たちで新しいリストを再構築する以外の選択肢がありません。

もし我々が何を我々が知っているか(同じhero.idを持った2つのオブジェクトは同じheroである)を告げる追跡関数(tracking function)を与えればAngularはこれを回避できます。以下はそのような関数です:

int trackByHeroes(int index, Hero hero) => hero.id;

これで NgForTrackByディレクティブにこの追跡関数をセットします。

<div *ngFor="let hero of heroes; trackBy:trackByHeroes">({{hero.id}}) {{hero.fullName}}</div>

<div *ngFor="let hero of heroes" *ngForTrackBy="trackByHeroes">({{hero.id}}) {{hero.fullName}}</div>

この追跡関数は総てのDOM変化を削除しません。Angularはもし同じsame-heroプロパティたちが変化いればそのDOM要素を更新します。しかしそのプロパティが変化していなければ — そして殆どの時間これらが変わっていない場合 — AngularはこれらのDOM要素たちをそのままにしておけます。このリストUIはよりスムースでより応答が早くなります。

以下はNgForTrackByの効果を示したものです。

訳者注:オンラインのデモも参考になります。




*とテンプレート

NgFor, NgIf, 及びNgSwitch組み込みディレクティブを学んできましたが、この構文の一風変わったもの、即ちディレクティブ名の前に現れるアスタリスク(*)を使いました。

この * はテンプレートの助けなしでHTMLのレイアウトを修正するディレクティブを読んだり書いたりし易くするための糖衣構文(syntactic sugar)です。 NgFor, NgIf, 及び NgSwitchの総てが<template>タグで囲まれている要素の副ツリーを追加または削除します。

* プレフィックス構文が我々がこれらのタグをスキップし、我々が含める、外す、または繰り返すHTML要素に直接集中できるようにしているので、<template>タグを見ることはありませんでした。

この節ではさらに中身を知り、如何にAngularが*を外し、HTML<template> タグに拡張しているかを知ります。



*ngIfの展開

我々はAngularがやっていることを自分たちで行い、 * プレフィックス構文をテンプレート構文に展開できます。以下は*ngIfを使ったコードです:

<hero-detail *ngIf="currentHero != null" [hero]="currentHero"></hero-detail>

currentHero2回参照されており、最初はNgIf true/false条件として、そしてHeroDetailComponentに渡される実際のheroとしてです。

最初の拡張ステップでは ngIf* プレフィックスなし)とその内容をテンプレート・ディレクティブに代入された式にします。

<hero-detail template="ngIf:currentHero != null" [hero]="currentHero"></hero-detail>

次の(そして最後の)ステップではHTML<template> タグと[ngIf] プロパティ・バインディングに展開します。

<template [ngIf]="currentHero != null">
  <hero-detail [hero]="currentHero"></hero-detail>
</template>

[hero]="currentHero"バインディングではこのテンプレートの内部の子供の<hero-detail>要素上に留まっていることに注意してください。



角括弧を忘れないこと

ngIf="currentHero"と間違って書かないでください!この構文は文字列の値"currentHero"ngIfに代入してしまい、ngIfがブール値を待っているのでこれは動きません。



*ngSwitchの展開

似たような変換が*ngSwitchに適用できます。我々は自分たちでこの構文を展開できます。以下がその例で、最初に*ngSwitchCase*ngSwitchDefaultで、次にまた<template>タグで展開します:

<span [ngSwitch]="toeChoice">


<!-- with *NgSwitch -->

<span *ngSwitchWhen="'Eenie'">Eenie</span>

<span *ngSwitchWhen="'Meanie'">Meanie</span>

<span *ngSwitchWhen="'Miney'">Miney</span>

<span *ngSwitchWhen="'Moe'">Moe</span>

<span *ngSwitchDefault>other</span>


<!-- with <template> -->

<template [ngSwitchWhen]="'Eenie'"><span>Eenie</span></template>

<template [ngSwitchWhen]="'Meanie'"><span>Meanie</span></template>

<template [ngSwitchWhen]="'Miney'"><span>Miney</span></template>

<template [ngSwitchWhen]="'Moe'"><span>Moe</span></template>

<template ngSwitchDefault><span>other</span></template>


</span>

*ngSwitchCase*ngSwitchDefault*ngIfと全く同じやり方で展開し、これまでの要素たちを<template>タグ間に包み込みます。

これでどうしてngSwitch自身がアスタリスク(*)のプレフィックスが付けられていないかお判りでしょう。これは内容を定義していません。その仕事はテンプレートたちの集まりを管理することです。

この場合は、ngSwitchCaseNgSwitchDefaultのディレクティブたちの2つのセットを管轄しています。Angularが選択されたテンプレートの値を2回表示するのが予期されます:1回は(*) プレフィックス付きのバージョンで、もう1回は展開したテンプレートのバージョンです。

それがまさしくこのサンプルで見ているものなのです:




*ngForの展開

*ngForは似たような変換を受けます。まず*ngForサンプルから始めましょう:

<hero-detail *ngFor="let hero of heroes; trackBy:trackByHeroes" [hero]="hero"></hero-detail>

ngForをテンプレート・ディレクティブに移した後の同じサンプルです:

<hero-detail template="ngFor let hero of heroes; trackBy:trackByHeroes" [hero]="hero"></hero-detail>

そして以下は<template>タグに展開し、オリジナルの<hero-detailを包み込んだものです。

<template ngFor let-hero [ngForOf]="heroes" [ngForTrackBy]="trackByHeroes">

<hero-detail [hero]="hero"></hero-detail>

</template>

リピータが設定に際し移動するパーツを持っているので、NgForNgIfよりは少しばかり複雑です。この場合、リストを特定するNgForOfディレクティブとNgForByを生成し代入することを思い出さねばなりません。*ngFor構文を使うのはこの展開したHTMLを自分自身で書くよりはずっと簡単です。



テンプレート参照変数 (Template reference variables)

テンプレート参照変数DOM要素またはテンプレート内のディレクティブへの参照です。

これはネーティブのDOM要素たちで使えますが、またAngularのコンポネントでも使えます - 実際これはどのカスタムのウェブ・コンポネントでも機能します。

訳者注:オンラインのデモも参考になります。



テンプレート参照変数の参照

我々は現行テンプレート内のどこにおいてもテンプレート参照変数を参照できます。

同じテンプレート内では一回以上同じ変数名を定義することはできません。実行時の値が予測不能になります。

以下はテンプレート参照変数の生成と消費の2つのサンプルです:

<!-- phone refers to the input element; pass its `value` to an event handler -->

<input #phone placeholder="phone number">

<button (click)="callPhone(phone.value)">Call</button>


<!-- fax refers to the input element; pass its `value` to an event handler -->

<input ref-fax placeholder="fax number">

<button (click)="callFax(fax.value)">Fax</button>

始めのサンプルではテンプレート参照変数phoneinput要素を参照し;その’value’をイベント・ハンドラに渡しています。2番目のサンプルでは同様にテンプレート参照変数のfaxinput要素を参照し、その’value’をイベント・ハンドラに渡しています。

"phone"のプレフィックスのハッシュ (#) は我々はphone変数を定義していることを意味します。

# 文字を使いたくない人はその標準的な代替物のref- プレフィックスが使えます。例えば、我々は#phoneまたは ref-phoneのどちらかを使ってphone変数を定義できます。



如何に変数はその値を取得するのか

Angularはその変数の値にその変数が定義された要素をセットします。我々はこれらの変数たちをinput要素上で定義しました。我々はこれらのinput要素のオブジェクトをbutton要素たちに渡し、そこでイベント・バインディングたちのなかのcallメソッドへの引数に使われています。



NgFormとテンプレート参照変数

最後のサンプルを見てみましょう--フォームです。テンプレート参照変数のためのイメージキャラクタです:

フォームのためのHTML「フォーム」の章で見たようにかなり難解なものになりがちです。以下は簡素化された例ですが—でもシンプルではありません。

<form (ngSubmit)="onSubmit(theForm)" #theForm="ngForm">
  <div class="form-group">
    <label for="name">Name</label>
    <input class="form-control" required ngControl="firstName"
      [(ngModel)]="currentHero.firstName">
  </div>
  <button type="submit" [disabled]="!theForm.form.valid">Submit</button>
</form>

テンプレート参照変数の theFormはこの例では3回出てきていて、長いHTMLのなかに分散しています。

<form (ngSubmit)="onSubmit(theForm)" #theForm="ngForm">
  <button type="submit" [disabled]="!theForm.form.valid">Submit</button>
</form>

theFormの値は何でしょうか?

もしAngularが引き継いでいなかったらこれは HTMLFormElementでしょう。これは実際ngFormで、Angularの組み込みNgFormディレクティブへの参照です。NgFormディレクティブはネーティブのHTMLFormElementを包み、ユーザ入力の有効性を追跡する機能といった付加的なスーパ-パワーを付与しています。

これは theForm.form.validをチェックすることでsubmitボタンをdisabledにし、リッチな情報を持ったオブジェクトを親のコンポネントのonSubmitメソッドに渡しています。

訳者注:オンラインのデモも参考になります。



入出力のプロパティ (Input and output properties)

これまでのところ我々はバインディング宣言の右側に出てくるテンプレート式と文内のコンポネントのメンバたちへのバインディングに主として説明してきました。その位置にあるメンバがデータ・バインディングの元(ソース)です。

本節ではバインディング宣言の左側にあるディレクティブのプロパティたちであるターゲットへのバインディングに絞って説明いたします。これらのディレクティブ・プロパティたちはinputs(入力)またはoutputs(出力)として宣言されねばなりません。

思い出してください:総てのコンポネントディレクティブです。



我々はデータ・バインディング・ターゲットとデータ・バインディング・ソースとをはっきり区別しています。

バインディングのターゲット=. 左側ソース=. 右側に位置します。

バインディングのターゲットはバインディング句読点の [], () または [()]のなかのプロパティまたはイベントです。ソースは引用符 (" ") の内側または内挿({{}})内のどちらかです。

ソース・ディレクティブの各メンバは自動的にバインディングに使えます。我々はテンプレート式または文内のディレクティブ・メンバをアクセスするのに何も特にすることはありません。

我々はターゲット・ディレクティブのメンバへのアクセスは限定されています。我々はinputsまたはoutputsと明示的に識別されたプロパティたちにのみバインドできます。

以下の例では、iconUrlonSave=.の右側の句読点構文内で参照されたあるコンポネントのメンバたちです。

<img [src]="iconUrl"/>

<button (click)="onSave()">Save</button>

これらはいづれもこのコンポネントの入力でも出力でもありません。これらはこれらのバインディングのデータ・ソースです。

HeroDetailComponentバインディングのターゲットの時を見てみましょう。

<hero-detail [hero]="currentHero" (deleteRequest)="deleteHero($event)">

</hero-detail>

HeroDetailComponent.heroHeroDetailComponent.deleteRequestはバインディング宣言の左側にあります。 HeroDetailComponent.hero角括弧内にあります;これはプロパティ・バインディングのターゲットです。

HeroDetailComponent.deleteRequestは丸括弧内にあります;これはイベント・バインディングのターゲットです。



入力と出力のプロパティを宣言する

ターゲットのプロパティはinputsまたはoutputsと明示的にマークされていなければなりません。

我々がHeroDetailComponentの内部を覗き見るときは、そこにはこれらのプロパティたちはアノテーションにより入力と出力のプロパティだとマークされているのが判ります。

@Input() Hero hero;

@Output() final deleteRequest = new EventEmitter<Hero>();



代替的には、次の例で示すように、我々はディレクティブ・メタデータの入力または出力リストのなかでメンバたちを識別できます。

@Component(
    // ...
    inputs: const ['hero'],
    outputs: const ['deleteRequest'],
    )

我々はアノテーションでまたはメタデータ・リストの中で入力/出力プロパティを指定できます。双方でではありません!



入力か出力か?

入力のプロパティは通常データの値を受け取ります。出力のプロパティは EventEmitterオブジェクトのようなイベントのプロデューサーを外部から見えるように(エクスポーズ)します。

入力と出力という用語はターゲット・ディレクティブの責任を反映したものです。


HeroDetailComponent.heroはデータがテンプレート・バインディング式からそのプロパティにデータが流れ込むので、HeroDetailComponentから見れば入力プロパティです。

HeroDetailComponent.deleteRequestはイベントがそのプロパティを、テンプレート・バインディング文の中のハンドラにむけて流れ出させるので HeroDetailComponentから見れば出力プロパティです。



/出力プロパティのエイリアス化

/出力プロパティの名前を内部の名前とは別にしたパブリックな名前にしたいことがあります。

これは属性ディレクティブで良く起きます。ディレクティブの消費者はそのディレクティブの名前にバインドすることを予期しています。例えば、あるディレクティブを<div>タグのmyClickセレクタで適用するときは、それもまたmyClickと呼ばれるイベント・プロパティにバインドすることを我々は期待します。

<div (myClick)="clickMessage=$event">click with myClick</div>

しかしながら、このディレクティブ名はそのディレクティブ・クラス内のプロパティの名前としてはしばしば適しません。ディレクティブ名にはそのプロパティが何をするかを記すことは滅多にありません。 myClickというディレクトリ名はクリック・メッセージを出すプロパティの名前としては良い名前ではありません。

幸い異なった名前を内部で使っている一方で、従来型の期待に合ったパブリックなプロパティ名を持たせることができます。

我々は以下のように input/outputアノテーションのなかにそのプロパティ名のエイリアス(別名)を指定できます:

// @Output(alias) [type info] propertyName = ...

@Output('myClick') final EventEmitter clicks = new EventEmitter<String>();



我々はまたinputsoutputsのリストの中でプロパティ名をエイリアス化できます。我々はコロンで区切られた文字列を書きます。左側がプロパティ名で、右側がパブリックなエイリアス名です。

@Directive(
    // ...
    outputs: const ['clicks:myClick']) // propertyName:alias




テンプレート式演算子 (Template expression operators)

テンプレート式の言語は、特別なシナリオ向けの幾つかの特別な演算子で補完されたDartの構文のサブセットを採用しています。ここでは我々はこれらの演算子の2つ、即ちパイプと安全ナビゲーションの演算子を解説します。



パイプ演算子( | )

ある式の結果はそれをバインディングのために使うには何らかの変換が必要になることがあります。例えば、ある数字を通貨表示したい、テキストを大文字で表示したい、あるいはあるリストをフィルタにかけそれをソートしたい、といった場合がそうです。

Angularのパイプはこれらのような小規模な転換に適しています。パイプは入力の値を受け付け変換した値を返す簡単な関数です。これらはテンプレート式の中でパイプ演算子(|)を使って簡単に適用できます:

<div>Title through uppercase pipe: {{title | uppercase}}</div>

パイプ演算子は左側の式の結果を右側の関数に渡します。

我々は複数のパイプを使って式のチェインを作ることができます:

<!-- Pipe chaining: convert title to uppercase, then to lowercase -->

<div>

Title through a pipe chain:

{{title | uppercase | lowercase}}

</div>

またパラメタたちをパイプに適用することもできます:

<!-- pipe with configuration argument => "February 25, 1970" -->

<div>Birthdate: {{currentHero?.birthdate | date:'longDate'}}</div>



安全ナビゲーション演算子( ?. )nullプロパティ・パス

安全ナビゲーション演算子(?.)はプロパティ・パス内のnullと未定義値から守るためのたやすく書けまた便利な手段です。ここにそれを示します。もし currentHeronullのとき起きるビュー描写のエラーを保護します。

The current hero's name is {{currentHero?.firstName}}



Dartの相違点:?.Dartの演算子です

安全ナビゲーション演算子(?.)Dart言語の一部(訳者注:null認知演算子)です。AngularTypeScriptJavaScriptのアプリであっても?.に対応しているので、これはテンプレート式演算子として考えられます。

この問題とソリューションに関し更に詳しく説明します。

titleプロパティにバインドされた以下のデータがnullのとき何が起きるでしょう?

The title is {{title}}

このビューは描画をしますが表示される値は空白です;我々は単に"The title is"のみが見え、そのあとには何もありません。それは妥当な振舞です。少なくともこのアプリはクラッシュしません。

次のサンプルで示すように、テンプレート式がプロパティ・パスを含んでいたとし、nullherofirstNameを表示するとしましょう。

The null hero's name is {{nullHero.firstName}}

Dartは例外をスローし、Angularもそうします:

EXCEPTION: The null object does not have a getter 'firstName'.

最悪です、ビュー全体が消えてしまいます。

heroプロパティは絶対nullになる筈がないと信じるのならこれは妥当な振舞だと主張することも可能でしょう。絶対nullになってはいけないものがnullになってしまうとしたら、我々は捕捉し直すべきプログラミング・エラーをしてしまったのです。例外をスローするのは正しいことです。

一方、プロパティ・パス内のnull値は時々、特に最終的にはデータが出てくることが判っている場合はOKです。

データを待っている間、ビューは文句を言うことなく描画しなければならず、nullのプロパティ・パスはtitleプロパティがやったようにブランクとして表示されるべきです。

残念ながら currentHeronullのときに我々のアプリはクラッシュしてしまいます。

我々はNgIfを使ってコードでこれを回避することは可能です。

<!--No hero, div not displayed, no error -->

<div *ngIf="nullHero != null">The null hero's name is {{nullHero.firstName}}</div>

これらのアプローチはメリットがありますが、特にプロパティ・パスが長いときは面倒です。 a.b.c.dといった長いプロパティ・パスのなかのどこかにあるnullに対して防護することを想像してください。.

Angularの安全ナビゲーション演算子(?.)はプロパティ・パスのなかのnullに対し防護するにはもっとたやすく書けまた便利な手段です。この式はこれが最初にnull値を見つけたときに対処してくれます。表示は空白ですがこのアプリはエラーなしで動き続けます。

<!-- No hero, no problem! -->

The null hero's name is {{nullHero?.firstName}}

これは a?.b?.c?.dといった長いプロパティ・パスでも完全に動作します。



まとめ

これでテンプレート構文の概観を完了しました。これで自分のコンポネントとディレクティブを書くなかでこの知識を活かすときです。





前のページ

次のページ