1.5.5AngularJSでは、アプリケーションをコンポーネントに分けて組み立てることができます。2にも採り入れられている仕組みです。大きなアプリケーションはコンポーネントを階層化して、機能ごとにコンポーネントを分けることにより、開発が柔軟に進められます。コンポーネントを使う」でつくった簡単なデータバインディングのサンプル001(図001)に、階層化したコンポーネントで機能を加えてみます。コントローラに定めた値をデータバインディングでHTMLドキュメントに表示するまずは、項目を複数表示できるようにします(図002)。このサンプル001では、ひとつの項目をコンポーネント(heroineDetail)に分けてHTMLドキュメントに加えました。すると、複数の項目をまとめる新たなコンポーネント('heroineList')が間に入ればよいでしょう。図002■複数の項目のコンポーネントがまとめて示されるメインのHTMLドキュメントに差し込むコンポーネントは、つぎのように複数項目をまとめる新たなコンポーネント(heroineList)に差し替えます。動的にリストをつくる」03「モジュールとコントローラをHTMLドキュメントに定める」参照)。項目ごとのオブジェクト(heroine)はコンポーネントの属性に加えました。heroine="heroine"></heroine-detail>ひとつひとつの項目を示すコンポーネントのテンプレート(heroineDetail.html)は、つぎのようにHTMLコードの構成を少しだけ変えます。{{$ctrl.heroine.name}}<br></div>メインのHTMLドキュメントは、コントローラの追加を外すとともに、複数項目をまとめるコンポーネントのJavaScriptファイル(heroineList.js)の読み込みを加えます。コントローラがなくなると、当然そこに設定されていた項目を表すオブジェクトのプロパティも失われます。前述のとおり、複数項目をまとめるコンポーネント(heroineList)が項目ごとのコンポーネント(heroineDetail)に同じ名前のプロパティ(heroine)を与えています。src="heroineDetail.js"></script>複数項目をまとめるコンポーネントのJavaScriptファイル(heroineList.js)には、コントローラ(HeroineListController)に複数の項目をオブジェクトの配列(list)として納めます[*001]。'シータ',}];}項目ごとのコンポーネントのJavaScriptファイル(heroineDetail.js)では、項目を示すオブジェクトが納められたプロパティ(heroine)にデータバインディングしています。値として与えられた文字列<は、=と異なり、親コンポーネントから子への一方向のデータバインディングを定めます。1.5から加えられた設定で、データ処理の流れを明らかにし、アプリケーションの負荷を下げるのに役立ちます。'='}});新たなコンポーネント(heroineList)を加えたことにより、HTMLドキュメントには複数の項目が示されるようになりました(前掲図002)。HTMLコードとJavaScriptファイルの記述は、つぎのコード001にまとめたとおりです。項目ごとのオブジェクトに加えた新たなプロパティ(content)の値は、今のところドキュメントには表示されません。この値の表示と編集の機能を、後でコンポーネントとして増やします。コンポーネントのコントローラに定めたコンストラクタ関数(HeroineListController)には、3つの引数($scopeと$elementおよび$attrs)を与えました。本稿の作例ではこれらの引数はとくに用いません。引数があることを示すために加えただけですので、省くことができます。コンポーネントから表示項目を削除するつぎに、複数項目が加えられたコンポーネントから、表示項目を削除できるようにしましょう(図003)。そのために、項目ごとのコンポーネントのテンプレート(heroineDetail.html)には、削除のボタン(<button>要素)を以下のように加えます。項目を調べて削除する」03「チェックのついた項目を要素のクリックで削除する」参照)。ng-click="$ctrl.onDelete()">削除</button></div>呼び出す関数(onDelete())は、JavaScriptファイル(heroineDetail.js)でコンポーネントのbindingsプロパティに文字列の値&を与えることにより、親コンポーネントにイベントとしてコールバックできます。'&'}});バインディングしたコールバック関数(onDelete())は、コンポーネントのテンプレート(heroineList.html)にハイフン"-"つなぎのチェインケースにして、つぎのようにディレクティブ(on-delete)のかたちで加えられます。その値として、改めてコンポーネントのメソッド(deleteHeroine())呼び出しを定めました。引数には、削除するオブジェクト(heroine)を渡します。on-delete="$ctrl.deleteHeroine(heroine)"></heroine-detail>項目を削除するメソッド(deleteHeroine())は、複数項目をまとめるコンポーネント(heroineList.js)に定めました。引数に受け取った削除するオブジェクトを、複数項目の配列(list)からArray.indexOf()メソッドによりインデックス番号で特定します(存在しないときの戻り値は-1)。そのうえで、そのエレメントをArray.splice()メソッドで除きます。1);}};}これで、項目ごとのコンポーネント(heroineDetail)のテンプレートに加えられたボタン(<button>要素)を押すと、項目リストのコンポーネント(heroineList)に定めたメソッド(HeroineListController())が呼び出されて、その項目は削除されます。ふたつのコンポーネントのテンプレートとJavaScriptファイルの中身をコード002にまとめました。メインのHTMLドキュメントの記述(<body>および<body>要素)は、前掲コード001のまま変えていません。項目のデータを増やして新たなコンポーネントで編集する項目リストのコンポーネント(heroineList)には、すでに前掲コード001でリストの配列(list)に納めた項目オブジェクトに、まだ使っていないプロパティ(content)が加えてありました。この値のテキストを、ただ表示するだけでなく、編集できるようにしましょう。そのためのコンボーネント(editableField)を新たに加え、項目ごとのコンポーネント(heroineDetail)のテンプレートにつぎのように定めます。プロパティ値は、新たなコンポーネントに後で加えるngModelディレクティブのプロパティ(fieldValue)の値としました。$ctrl.heroine})">削除</button></div>テキスト編集のテンプレート(editableField.html)には、以下のようにボタン(<button>要素)を加えて、クリックしたときngClickディレクティブでこの後コンポーネント(editableField)に定めるメソッド(handleModeChange())を呼び出します。このメソッドはプロパティ(editMode)のブール(論理)値を切り替えます。この値はngSwitchディレクティブに定めました。Insider「式の値によって表示を切り替えるには?(ng-switch)」参照)。<input>要素にはngSwitchWhenディレクティブに文字列trueを与えましたので、プロパティ値がtrueのとき表示されます。}}で加えたプロパティ(fieldType)の値は"text"としますので、1行入力フィールドとなり、ngModelディレクティブに定めたプロパティ(fieldValue)の値が示されます。}}に納めたプロパティ値がテキストとして加えられます(図004)。:でふたつのテキストを与えましたので、条件のプロパティ(editMode)のブール値によってそれらが切り替わることになります。'編集'}}</button>テキスト編集のコンボーネント(editableField)には、つぎのようなJavaScriptコードを定めます。コントローラ(EditableFieldController)に、ふたつのプロパティ(fieldValueとfieldType)をバインディングしました。ひとつ(fieldValue)は、項目ごとのコンポーネントのテンプレート(heroineDetail.html)に定められたプロパティで、項目リストのコンポーネント(heroineList)の配列(list)に納めた項目オブジェクトから取り出されたプロパティ(content)の値が割り当てられています。これが新たに加えるテキストです。バインディングされたもうひとつのプロパティ(fieldType)は、テンプレートに加えた<input>要素のtype属性の値として用いられています。Binding」参照)。このバインディングされたプロパティの初期値は、コントローラ(EditableFieldController)につぎのように定められた$onInit()メソッドが与えます。このメソッドはHTMLドキュメントのエレメントに加えたコントローラの生成とデータバインディングがすべて済んだときに呼び出されます。'text';}};}メインのHTMLドキュメントには、新たに定めたコンポーネント(editableField)のJavaScriptファイルの読み込みも加えます。これで編集したテキストを保存して表示することができます。リセットボタンでテキストを編集前に戻す仕上げにリセットボタンを加えて、編集しているテキストを編集前の値に戻せるようにしましょう。テキスト編集のコンポーネントのテンプレート(editableField.html)には、つぎのようにリセットボタン(<button>要素)を記述します。ngIfディレクティブを与えましたので、定めたプロパティ(editMode)がtrueのとき、つまりテキストを編集する場合にだけ要素はつくられます。ngClickディレクティブにコントローラのメソッド(reset())呼び出しを書き加えました。ng-click="$ctrl.reset()">リセット</button>テキスト編集のコンポーネントのJavaScriptファイル(editableField.js)に、編集をリセットするメソッド(reset())はつぎのように加えます。編集状態に切り替えるメソッド(handleModeChange())で編集前のプロパティ(fieldValue)のテキストを変数(fieldValueOld)にとっておき、リセットされたらその値を戻すというだけです。変数の初期値は$onInit()メソッドで取り出してあります。ctrl.fieldValue;};}これで、編集し始めてから、保存前にリセットボタンを推すと、編集前のテキストに戻せます。テキストが空の状態から入力したのなら、リセットボタンでフィールドは再び空になります(図005)。動きとしては、これででき上がったように見えます。このままでは、データバインディングが正しく行われていません。つぎに項を改めてご説明しましょう。一方向のバインディングでデータの変更を親コンポーネントに反映させるまず、前述03「項目のデータを増やして新たなコンポーネントで編集する」の機能を使って、初期値が空だった項目のプロパティ(content)に前掲図004のようにテキスト(「風の谷のナウシカ」)を入力して保存します。項目のオブジェクトの初期値は、つぎのように項目リストのコンポーネント(heroineList)に定められていました。保存されたテキストは、正しくテキスト編集のコンポーネント(editableField)のテンプレートに示されます。{};}つぎに、今保存した項目を項目リストから除きます。コンポーネント(heroineList)に定めた削除のメソッド(deleteHeroine())には、あらかじめつぎのような確認用のふたつのconsoleのメソッドを加えておきました。Chromeのコンソールには、以下の図006のような結果が示されます。確認用};}削除しようとしている項目のオブジェクトのプロパティ(content)に、編集して保存したテキストが与えられておらず初期値の空のままです。削除の処理の後に配列の長さが1になったのは、エレメントのオブジェクトが正しく除かれたことを表しています。図006■コンソールに示された削除する項目のオブジェクトと配列の長さテキスト編集のコンポーネント(editableField)に入力した値が、親の項目リストのコンポーネント(heroineList)のオブジェクトに反映されなかったのは、つぎのようにプロパティ(fieldValue)のバインディングが一方向の<で定められていたからです。これを双方向の=に変えれば、親コンポーネントの値も連動するようになります。architecture」参照)。双方向でデータを監視することは、アプリケーションの負荷を高めます。'<',}});項目の削除が正しくできたのは、データをもつ項目リストのコンポーネント(heroineList)に削除のメソッドを定め、子のコンポーネント(heroineDetail)がコールバックでそのメソッドを呼び出したからです。テキストの編集についても親コンポーネントにそのためのメソッドを加えて、子のコンポーネントからはコールバックで実行すればよいでしょう。すると、データはそのもち主のコンポーネントが操作することになって、処理の流れもわかりやすくなります。そこでまず、テキスト編集のコンポーネント(editableField)に、コールバック(onUpdate)のバインディング(&)を加えます。このコールバックは、編集と保存の切り替えるメソッド(EditableFieldController())が、テキストを保存するときに呼び出します。引数はオブジェクトで、そのプロパティ(value)の値に編集されたテキストを与えました。fieldValue});}};}テキスト編集のコンポーネント(editableField)を差し込んだ項目ごとのコンポーネント(heroineDetail)には、コールバックをディレクティブのかたちで与え、コンポーネントのメソッド(update())を呼び出しています。引数には文字列('content')と、前述のコールバック呼び出しで受け取ったオブジェクトからプロパティ(value)の値を取り出して渡します。ng-click="$ctrl.onDelete()">削除</button></div>項目ごとのコンポーネント(heroineDetail)は、まだ書き替えるデータのもち主ではありません。そこで、さらにコールバック(onUpdate)のバインディング(&)を加えます。呼び出されたメソッド(update())からバケツリレーのようにコールバックをつなぐのです。value});};}データのもち主は、項目リストのコンポーネント(heroineList)です。そこに差し込んだ項目ごとのコンポーネント(heroineDetail)に、つぎのようにコールバックのディレクティブを与え、テキスト編集のメソッド(updateHeroine())を呼び出しました。value)"></heroine-detail>項目リストのコンポーネント(heroineList)に定めたテキスト編集のメソッド(updateHeroine())は、つぎのように引数に受け取った項目のオブジェクト(heroine)のプロパティ(prop)に新たなテキスト(value)を定めます。これで、前述と同じテキスト編集と保存の操作をすると、コンポーネントのデータが正しく書き替えられました(図007)。3つのコンポーネントのテンプレートとJavaScriptファイルの中身は、以下のコード003にまとめたとおりです(メインのHTMLドキュメントの記述は前掲コード001のまま変えていません)。サンプル001に作例をフレームに入れて掲げました。