ディレクティブはAngularJSアプリケーションで最も重要な部分です。AngularJSはすでに非常に豊富なディレクティブセットを提供していますが、アプリケーション固有のディレクティブを作成する必要があります。このチュートリアルでは、ディレクティブをカスタマイズする方法と、実際のプロジェクトでの使用方法を説明します。この投稿では***、Angularディレクティブを使って簡単なメモ帳アプリケーションを作成する方法を説明します。
中抜き
ディレクティブは新しい HTML 構文を導入するために使われます。ディレクティブは DOM 要素のマークアップで、要素に特定の動作を与えます。例えば、静的な HTML は日付ピッカーコントロールの作成と表示方法を知りません。HTML にこの構文を認識させるには、ディレクティブを使う必要があります。ディレクティブは、何らかの方法で日付選択をサポートする要素を作成します。これをどのように実現するかについては、ステップバイステップのガイドがあります。 もしあなたがAngularJSアプリケーションを書いたことがあるなら、気づいているかどうかに関わらず、すでにディレクティブを使っているはずです。ng-mode、ng-repeat、ng-showなどの簡単なディレクティブを使ったことがあるはずです。これらのディレクティブは、それぞれ DOM 要素に固有の振る舞いを与えます。例えば、ng-repeat は特定の要素を繰り返し表示し、ng-show は要素を条件付きで表示します。要素をドラッグ&ドロップに対応させたい場合は、それを実装するディレクティブを作成する必要があります。ディレクティブの基本的な考え方は簡単です。イベントリスナーを要素にバインドしたり、DOMを変更することで、HTMLに本当のインタラクティブ性を与えます。
jQueryの視点
jQueryを使って日付ピッカーを作ることを想像してみてください。まず、HTMLにプレーンな入力ボックスを追加し、jQueryで$.dataPicker()を呼び出して日付ピッカーにします。しかし、よく考えてください。デザイナーがやってきてHTMLのマークアップを見たとき、そのフィールドが実際に何を表しているのかすぐに想像できるでしょうか?単なる入力ボックスでしょうか、それとも日付ピッカーでしょうか?これらを判断するにはjQueryのコードを見る必要があります。代わりにAngularのアプローチでは、HTMLを拡張するためにディレクティブを使用します:
<input type="text" />
そうかもしれませんね:
<input type="text" />
このUIフォーメーションの作り方は、よりわかりやすく明確です。エレメントを見ただけで、これが何なのか簡単に理解できます。
カスタムインストラクションの作成
1.新しいHTML要素 2.要素の属性 3.CSSクラス 4.アノテーション もちろん、ディレクティブがHTML上でどのように表示されるかを制御することは可能です。典型的なディレクティブがAngularJSでどのように書かれるかを見てみましょう。ディレクティブはコントローラと同じように登録されますが、ディレクティブの設定プロパティを持つ単純なオブジェクトを返します。次のコードは単純な Hello World ディレクティブです。
var app = angular.module('myapp', []);
app.directive('helloWorld', function() {
return {
restrict: 'AE',
replace: 'true',
template: '<h3>Hello World!!</h3>'
};
});
<hello-world/>
//OR
<hello:world/>
または、属性として使用します:
<div hello-world></div>
//OR
<div hello:world/>
HTML5の仕様に合わせたい場合は、要素の前にx-またはdata-をつけます。したがって、以下のマークアップもhelloWorldディレクティブに適合します:
<div data-hello-world></div>
//OR
<div x-hello-world></div>
注意: ディレクティブをマッチさせるとき、Angular は要素や属性の名前から x- や data- という接頭辞を取り除きます。 そして - や : を連結した文字列をこぶ表現に変換し、登録されているディレクティブとマッチさせます。これが、HTMLでhelloWorldディレクティブがhello-worldと同じように使われる理由です。実際には、HTMLではタグや属性で大文字と小文字が区別されないことに関係しています。 上記のディレクティブは静的なテキストの表示を可能にするだけですが、ここには興味深い点があります。ディレクティブを設定するために、ディレクティブ定義プロセスで使用される属性が3つあります。以下、一つずつ説明します。
- restrict - この属性は、ディレクティブがHTMLでどのように使われるかを指定するために使われます。上の例では、'AE' が使われています。ですから、このディレクティブは新しい HTML 要素や属性として使用することができます。ディレクティブをクラスとして使えるようにするには、 restrict を 'AEC' にします。
- template - この属性は、ディレクティブが Angular によってコンパイルされ、リンクされたときに生成される HTML マークアップを指定します。この属性の値は単純な文字列である必要はありません。テンプレートは非常に複雑で、しばしば他のディレクティブや式などを含みます。テンプレートの代わりにtemplateUrlを見ることが多いので、理想的にはテンプレートを特定のHTMLファイルに入れてtemplateUrl属性を指定します。
- replace - この属性は、生成された HTML の内容がディレクティブを定義している HTML 要素を置き換えるかどうかを指定します。この例では、ディレクティブは <hello-world></hello-world> として使われ、 replace は true に設定されています。に置き換わります。最終的な出力は <h3>Hello World!!!</h3> です。</h3>.replace をデフォルト値の false に設定すると、生成されたテンプレートはディレクティブを定義する要素に挿入されます。
これを開き、"Hello World!!!"の中でよりグラフィカルに理解するために、右クリックして要素の内容を検査します。
リンクの機能と範囲
ディレクティブが生成するテンプレートは、特定のスコープでコンパイルされない限り、あまり意味がありません。デフォルトでは、ディレクティブは新しいサブスコープを作成せず、親スコープを使用します。 つまり、ディレクティブがコントローラの下に存在する場合は、そのコントローラのスコープを使用します。 スコープを使用する方法は、link と呼ばれる関数です。これは、ディレクティブ定義オブジェクトの link 属性で設定します。HelloWorld ディレクティブを変更して、ユーザが入力ボックスに色の名前を入力すると、Hello World テキストの背景色が自動的に変わるようにしてみましょう。また、ユーザが Hello World テキストをクリックすると、背景色は白に戻ります。 対応する HTML マークアップを以下に示します:
<body ng-controller="MainCtrl">
<input type="text" ng-model="color" placeholder="Enter a color" />
<hello-world/>
</body>
変更後のhelloWorldディレクティブは以下のようになります:
app.directive('helloWorld', function() {
return {
restrict: 'AE',
replace: true,
template: '<p style="background-color:{{color}}">Hello World',
link: function(scope, elem, attrs) {
elem.bind('click', function() {
elem.css('background-color', 'white');
scope.$apply(function() {
scope.color = "white";
});
});
elem.bind('mouseover', function() {
elem.css('cursor', 'pointer');
});
}
};
});
コマンド定義のリンク関数に注目してください。 これには3つの引数があります:
- elem - ディレクティブの jQLite (jQuery のサブセット) ラッパー DOM 要素。jQueryでAngularJSを導入した場合、この要素はjQLite要素ではなくjQuery要素です。この要素はすでにjQuery/jQLiteによってラップされているので、DOM操作に$()を使う必要はありません。
- attr - ディレクティブが置かれる要素の属性を含む正規化された引数オブジェクト。例えば、HTML要素:に何らかの属性を追加した場合、attrs.someAttribute.
リンク関数は、DOM要素にイベントリスナーを追加したり、モデル属性の変更を監視したり、DOMを更新したりするために使用されます。 上記のディレクティブのコードスニペットでは、clickとmouseoverの2つのイベントが追加されています。clickハンドラは<p>の背景色をリセットするために使用され、mouseoverハンドラはマウスをポインタに変更します。テンプレートには {{color}} という式があり、親スコープ内の色が変わったときに Hello World テキストの背景色を変更します。 これはこれらの概念を示しています。
#p#
コンパイル機能
コンパイル関数は、リンク関数の中で実行され、DOMを修正します。以下の引数をとります:
- tElement - 命令が配置される要素.
- attrs - 要素に与えられた引数の正規化リスト.
コンパイル関数はスコープにアクセスできず、リンク関数を返さなければならないことに注意してください。しかし、コンパイル関数を設定しなければ、リンク関数を通常通り設定することができ、コンパイル関数は以下のように書くことができます:
app.directive('test', function() {
return {
compile: function(tElem,attrs) {
//do optional DOM transformation here
return function(scope,elem,attrs) {
//linking function here
};
}
};
});
ほとんどの場合、link 関数だけを使う必要があります。なぜなら、ほとんどのディレクティブでは、イベントリスナーの登録、モデルの監視、DOM の更新を考えるだけでよく、これらはすべて link 関数で実行できるからです。 しかし、ng-repeat のように DOM 要素を複製して何度も繰り返す必要があるディレクティブでは、link 関数はコンパイル関数によって実行されます。なぜ生成プロセスを完了させるために2つの別々の関数が必要なのか、なぜ1つだけではだめなのかという疑問がわきます。この疑問に答えるには、Angularでディレクティブがどのようにコンパイルされるかを理解する必要があります!
指示のまとめ方
アプリケーションのブートストラップが開始するとき、Angularは$compileサービスを使用してDOM要素のトラバースを開始します。このサービスは登録されたディレクティブに基づいてマークアップテキスト内のディレクティブを検索します。すべてのディレクティブが特定されると、Angularはそれらのコンパイルメソッドを実行します。前述したように、compileメソッドは後で実行されるリンク関数のリストに追加されるリンク関数を返します。これをコンパイルフェーズと呼びます。ディレクティブが何度もクローンされる必要がある場合、コンパイル関数はコンパイルフェーズで一度だけ実行され、テンプレートをコピーしますが、リンク関数はコピーされるインスタンスごとに実行されます。そのため、処理を分けることでパフォーマンスが向上します。コンパイル関数でスコープオブジェクトにアクセスできない理由もここにあります。 コンパイルフェーズが終わると、リンクフェーズが始まります。このフェーズでは、収集されたすべてのリンク関数が1つずつ実行されます。ディレクティブによって作成されたテンプレートは正しいスコープで解析・処理され、イベントレスポンスを持つ実際の DOM ノードが返されます。
指導範囲の変更
デフォルトでは、ディレクティブは親コントローラのスコープを取得します。親コントローラのスコープがディレクティブに公開されている場合、 ディレクティブはそのスコープのプロパティを自由に変更することができます。ディレクティブが内部でのみ使用するプロパティやメソッドを追加したい場合もあるでしょう。親スコープにそれらを追加すると、親スコープを汚染することになります。 実際には、他に 2 つのオプションがあります:
- 子スコープ - このスコープのプロトタイプは、子である親スコープを継承します。
- 孤立スコープ - 親スコープを継承せず、孤立して存在するスコープ。
このようなスコープは、ディレクティブ定義オブジェクトの scope プロパティで設定することができます。次のコードはその例です:
app.directive('helloWorld', function() {
return {
scope: true, // use a child scope that inherits from parent
restrict: 'AE',
replace: 'true',
template: '<h3>Hello World!!</h3>'
};
});
上のコードでは、Angular に親の socpe を継承したディレクティブの新しい子スコープを作成するように指示しています。 別の方法として、分離されたスコープがあります:
app.directive('helloWorld', function() {
return {
scope: {}, // use a new isolated scope
restrict: 'AE',
replace: 'true',
template: '<h3>Hello World!!</h3>'
};
});
このディレクティブは、再利用可能なディレクティブを作成するときに便利な 孤立スコープを使用します。分離されたスコープを使うことで、ディレクティブは自己完結性が保証され、 HTML アプリケーションに簡単に挿入することができます。 内部的には親スコープにアクセスできないので、親スコープが汚染されることはありません。 helloWorld ディレクティブの例では、スコープが {} に設定されている場合、上のコードは動作しません。 新しい孤立スコープが作成され、対応する {{colour}} 式はこの新しいスコープを指すことになり、その値は未定義になります。 孤立スコープを使用したからといって、親スコープのプロパティにまったくアクセスできないというわけではありません。実際、親スコープのプロパティにアクセスし、その変更を監視するテクニックもあります。これらのテクニックについては、ディレクティブについてのこの連載の 後編で、Controller 関数などのより高度な概念とともに説明します。 第2部では、Angularディレクティブを使ってよりリッチなメモ帳アプリケーションを作成します。 ご期待ください。