[AngularJS] 7. ディレクティブ

---
[目次](http://qiita.com/Koizumi-Greenwich/items/3e327e3331b2f728797e)
≪前の記事 6.[カスタムフィルタ](http://qiita.com/Koizumi-Greenwich/items/87e447fd329fd7c00fa6)
≫次の記事 8.[カスタムディレクティブ](http://qiita.com/Koizumi-Greenwich/items/35becc74c18b2e854d09)

---

#ディレクティブとは
*ディレクティブ*は、AngularJSテンプレートを記述するためにHTMLを拡張したものです。
記述方法によって3種類あります。

- HTML要素
- HTML属性
- クラス値

これまでの例ではHTML属性のタイプしか使用していませんが、その他にHTMLタグ、クラス値として記述するものがあります。これらいずれも **ディレクティブ** と呼びます。

##ディレクティブの名前
これまでディレクティブは ng-xxxx-yyyy という書き方をしてきました。
しかしディレクティブ名の本名は ngXxxxYyyy というキャメルケースで表されます。

- ng-controller → 本名 ngController
- ng-model → 本名 ngModel
- ng-bind-html → 本名 ngBindHtml

といった具合です。

また、テンプレート内での以下の記述は、どれも同じディレクティブを意味しています。

- x-ng-model
- data-ng-model
- ng:model
- ng_model
- ng-model

これらはすべて `ngModel` ディレクティブに正規化されます。

- 先頭の `x-` および `data-` を取り除く
- `:`, `_`, `-` の区切りを取り除いてキャメルケース化する

これらの記述は、何らかの理由でディレクティブ名がそのまま使用できない場合に有用です。たとえばHTML要素名や属性名と同じ名前（例：table, name, idなど）のディレクティブの場合などです。

#主な組み込みディレクティブ
AngularJSには多数の組み込みディレクティブがあります。
ここではこれまで出てきたもの以外でよく使われるディレクティブをいくつかご紹介します。
その他のディレクティブについては [こちら](http://js.studio-kingdom.com/angularjs/ng_directive) を参照してください。

##ngClass

```html:ngClass書式
<要素 ng-class="{式}"> .... </要素>
```

式の結果をclass属性にセットします。
演算結果や条件式、関数の返り値などでclassを変化させることが出来ます。

```html:ngClass例
<p ng-class="flag ? 'is-true' : 'is-false'">
    <input type="checkbox" ng-model="flag">
    チェックボックスon/offでclassが変化する
</p>
```

##ngRepeat | ngRepeatStart | ngRepeatEnd
要素を繰り返す、いわゆる繰り返し制御を行うディレクティブです。

- ngRepeat
	- ngRepeat が指定された要素を繰り返します
- ngRepeatStart | ngRepeatEnd
	- ngRepeatStart が指定された要素から、ngRepeatEnd が指定された要素までの間を繰り返します。

ngRepeatの繰り返しは、PHPのforeach文のように、配列やハッシュオブジェクト（合わせてコレクションと言います）に基づいて行われます。

###書式(1)
```html:ngRepeat書式(1)
<要素 ng-repeat="変数 in コレクション"> ... </要素>
```

要素をコレクションの要素数だけ繰り返します。
繰り返し毎にコレクションの要素を`変数`にセットします。

```html:ngRepeat例(1)
<ul>
    <li ng-repeat="item in collection">{{item}}</li>
</ul>
```

###書式(2)
```html:ngRepeat書式(2)
<要素 ng-repeat="(キー変数, 要素変数) in コレクション"> ... </要素>
```

この書式では、繰り返し毎にコレクションのキーと要素それぞれ `キー変数`と`要素変数`にセットします。

```html:ngRepeat例(2)
<ul>
    <li ng-repeat="(key, item) in collection">{{key}}: {{item}}</li>
</ul>
```

###書式(3)
```html:ngRepeat書式(3)
<要素 ng-repeat-start="(キー変数, 要素変数) in コレクション"> ... </要素>
...
<要素 ng-repeat-end>
```

ngRepeatでは1つの要素の繰り返ししかできませんが、ngRepeatStartとngRepeatEndを使用することで、複数の要素を繰り返すことができます。

```html:ngRepeat例(3)
<dl>
    <dt ng-repeat-start="(key, item) in collection">{{key}}</dt>
    <dd ng-repeat-end>{{item}}</dd>
</dl>
```

###特別な変数
繰り返しの名がでは、以下の特別な変数を利用することが出来ます。

- $index
	- 何回目の繰り返しかを表します（0から開始）
- $first
	- 繰り返しの最初であればtrue
- $middle
	- 繰り返しの間（最初と最後ではない）ときにtrue
- $last
	- 繰り返しの最後であればtrue
- $even
	- $indexが偶数であればtrue
- $odd
	- $indexが奇数であればtrue

###ngClassEven | ngClassOdd

ngClassのサブセットで、ngRepeatの繰り返し内で利用できるディレクティブです。
それぞれ繰り返しの偶数回/奇数回でセットするclassを指定することができます。

```html:ngRepeat例(4)
<ul>
    <li ng-repeat="item in collection" 
        ng-class-even="'is-even'" ng-class-odd="'is-odd'">{{item}}</li>
</ul>
```

##ngSwitch
switch構文と同じ働きをするディレクティブです。

```html:ngSwitch書式
<要素 ng-switch="式">
    <子要素 ng-switch-when="値1"> ... </子要素>
    <子要素 ng-switch-when="値2"> ... </子要素>
    <子要素 ng-switch-default> ... </子要素>
</要素>
```

`ng-switch`で指定した式の値が、`ng-switch-when`で指定された値と等しければ、その子要素が表示されます。
どの`ng-switch-when`にもマッチしなければ、`ng-switch-default`の子要素が表示されます。

```html:ngSwitch例
<select ng-model="selection">
    <option value=""></option>
    <option value="red">赤</option>
    <option value="blue">青</option>
    <option value="green">緑</option>
</select>
<p ng-switch="selection">
    <span ng-switch-when="red">赤を</span>
    <span ng-switch-when="blue">青を</span>
    <span ng-switch-when="green">緑を</span>
    <span ng-switch-default>未</span>
    選択
</p>
```

##ngHref
href属性にバインドタグ`{{...}}`を含めると、期待通りの動作をしない場合があります。
バインドタグを含める場合はngHref属性を使いましょう。

```html:ngHref例
<a ng-href="http://foo.bar/?param={{param}}">リンク</a>
```

##ngSrc
href属性と同様、src属性でも問題が生じる場合があります。

```html:ngSrc例
<img ng-src="http://foo.bar/?param={{param}}">
```

##HTML要素ディレクティブ
AngularJSでは要素型ディレクティブを用いて、いくつかのHTML要素の振る舞いを変更しています。

###a
AngularJSテンプレート内のa要素では、href属性が空の場合にページ遷移やリロードなどのアクションを行わないようになっています。したがって`ng-click`でのイベント処理時に気を遣わずに済みます。

###form
[初めの一歩](http://qiita.com/Koizumi-Greenwich/items/5f1799589eb335c6d68e)でも述べたように、form要素は [FormController](http://js.studio-kingdom.com/angularjs/ng_types/form_controller) をオブジェクト化したディレクティブになっています。

また、HTMLのform要素と異なり、action属性が必須では無く、action属性が空の場合もsubmitによる送信アクションが行われなくなっています。

####class
form要素には以下のclassが自動的にセットされます。

- フォームが手つかずのとき ... ng-pristine
- フォームに手が加えられているとき ... ng-dirty
- フォームの入力値がすべて適正なとき ... ng-valid
- フォームのいずれかの入力値が不適正なとき ... ng-incalid

####name属性
form要素にname属性が指定されると、スコープ内に指定された名前で [FormControllerオブジェクト](http://js.studio-kingdom.com/angularjs/ng_types/form_controller) がセットされ、FormControllerオブジェクトに直接アクセスできるようになります。

####FormControllerプロパティ
`name属性値.プロパティ名`でアクセス可能です。

- $pristine ... フォームが手つかずのときにtrue
- $dirty ... フォームに手が加えられているときにtrue
- $valid ... フォームの入力値が全て適正なときにtrue
- $invalid ... フォームのいずれかの入力値が不適正なときにtrue
- $error ... フォームのいずれかの入力値が不適正なときに `{エラー名: [エラーとなっているフォーム要素または入力要素, ...], ...}`の形のハッシュオブジェクト


###各入力要素（input | textarea | select）
フォーム入力要素もディレクティブ化されています。
以下は各入力要素で共通の属性ディレクティブです。

####ngModel
[初めの一歩](http://qiita.com/Koizumi-Greenwich/items/5f1799589eb335c6d68e)で解説したように、データモデルとの紐付けを行うディレクティブ属性です。

####name
AngularJSではname属性をFormControllerオブジェクト内でのプロパティ名として使用します。

`FormControllerオブジェクト.name属性値`

とすることで入力要素自身のオブジェクトにアクセスすることができます。

また前述の `$pristine`、`$dirty`、`$valid`、`$invalid`、`$error`の各プロパティも含まれており、

`FormControllerオブジェクト.name属性値.$xxxx`

とすることでアクセス可能です。

####required
入力必須項目であることを示します。
入力が無い場合にバリデーション・エラーとなります。

###input type="text | password | email | url" | textarea
以下は`type="text"`、`type="password"`、`type="email"`、`type="url"`のinput要素、およびtextarea要素で使用可能な属性ディレクティブです。

####ngMinlength
最小文字数を指定します。入力文字数が指定した値より小さい場合はinvalid（不適正）となります。

####ngMaxlength
最大文字数を指定します。入力文字数が指定した値より大きい場合はinvalid（不適正）となります。

####ngPattern
正規表現を指定することで入力文字列のパターンチェックを行います。パターンにマッチしない場合にinvalid（不適正）となります。

`type="email"`、`type="url"`の場合は、HTML5の動作によってそれぞれ適切な入力のみが受け付けられます。HTML5が受け付けた入力からさらに妥当性を絞り込み対場合にngPatternを使用します。

####ngTrim
AngularJSでは入力値の前後のホワイトスペースを削除（トリミング）します。
ngTrimにfalseを指定するとトリミングを行わなくなります。

###input type="number"
以下は、数字入力用の`type=number`のinput要素で使用可能な属性ディレクティブです。
`type=number`はHTML5の動作によって数字のみが受け付けられます。

####min
最小値を指定します。指定した値より小さい場合はinvalid（不適正）となります。

####max
最大値を指定します。指定した値より大きい場合はinvalid（不適正）となります。

###input type="checkbox"

####ngTrueValue
チェックボックスがチェックされているときの入力値（value値）を指定します。

####ngFalseValue
チャックボックスがチェックされていない時の入力値（value値）を指定します。

###input type="radio"

####ngValue
等時をボタンがチェックされた場合の値を、式で指定することができます。
value属性との併用はできません。

###select
select要素ディレクティブでは、ngOptions属性によりoption要素およびoptgroup要素を自動的に生成します。

####ngOptions
コレクション（配列、ハッシュオブジェクト）にもとづいて、option要素、optgroup要素を生成するディレクティブです。

#####ngOptionsの書式
`選択項目 for 要素変数 in コレクション`


"選択値 as ラベル for 値 in 配列"
"ラベル group by グループ for 値 in 配列"
```

###フォーム＆入力要素の例
```html:フォーム＆入力要素の例
<h2>form</h2>
<form name="form">
    <p ng-show="form.$invalid">不適正な入力あり</p>
    <p>
        テキスト：<input type="text" ng-model="text" name="text" required ng-maxlength="10" ng-pattern="/^[A-Za-z0-9]+$/">
        <span ng-show="form.text.$error.required">未入力</span>
        <span ng-show="form.text.$error.maxlength">文字数オーバー</span>
        <span ng-show="form.text.$error.pattern">英数字以外の文字</span>
    </p>
    <p>
        メールアドレス：<input type="email" ng-model="email" name="email" required>
        <span ng-show="form.email.$error.required">未入力</span>
    </p>
    <p>
        数字：<input type="number" ng-model="number" name="number" min="10" max="20"> ※10〜20
        <span ng-show="form.number.$invalid">範囲外の数字</span>
    </p>
    <p>
        <input type="radio" ng-model="check" value="OK">OK
        <input type="radio" ng-model="check" value="NG">NG<br>
        OKを選択すると↓がチェックされる
    </p>
    <p>
        チェックすると↑でOKが選択される<br>
        <input type="checkbox" ng-model="check" ng-true-value="'OK'" ng-false-value="'NG'"> OK
    </p>
    <p>
        配列タイプ：
        <select ng-model="arraySelect" ng-options="item for item in arrayCollection"></select><br>
        ハッシュタイプ：
        <select ng-model="hashSelect" ng-options="key as item for (key, item) in hashCollection"></select><br>
        グループタイプ：
        <select ng-model="groupSelect" ng-options="key as item.name group by item.group for (key, item) in groupCollection"></select><br>
    </p>
</form>
```

#サンプルコード
```html:sample07.html
<!DOCTYPE html>
<meta charset="UTF-8">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
<script>
/**
 * アプリケーション モジュール オブジェクトの定義
 */
SampleModule = (function() {
    var module = angular.module("sampleModule", []);
    
    return module;
})();

/**
 * コントローラの定義
 */
(function(module) {
    /**
     * SampleController
     */
    module.controller("sampleController", function($scope){
        $scope.hashCollection  = {list1: "リスト1", list2: "リスト2", list3: "リスト3"};
        $scope.arrayCollection = ["要素1", "要素2", "要素3"];
        $scope.groupCollection = {
            list1: {name: "リスト1", group: "グループ1"},
            list2: {name: "リスト2", group: "グループ1"}, 
            list3: {name: "リスト3", group: "グループ2"},
            list4: {name: "リスト4", group: "グループ2"},
        };
    });
})(SampleModule);
</script>

<style>
    p.is-true {
        color: red;
    }
    p.is-false {
        color: black;
    }
    li.is-even {
        color: blue;
    }
    li.is-odd {
        color: green;
    }
</style>
<!-- sampleModule テンプレート -->
<div ng-app="sampleModule">
    <!-- sampleController テンプレート -->
    <div ng-controller="sampleController" ng-cloak>
        <h2>ngClass</h2>
        <p ng-class="flag ? 'is-true' : 'is-false'">
            <input type="checkbox" ng-model="flag">
            チェックボックスon/offでclassが変化する
        </p>
        
        <h2>ngRepeat</h2>
        <h3>例(1)</h3>
        <ul>
            <li ng-repeat="item in hashCollection">{{item}}</li>
        </ul>
        <h3>例(2)</h3>
        <ul>
            <li ng-repeat="(key, item) in hashCollection">{{key}}: {{item}}</li>
        </ul>
        <h3>例(3)</h3>
        <dl>
            <dt ng-repeat-start="(key, item) in hashCollection">{{key}}</dt>
            <dd ng-repeat-end>{{item}}</dd>
        </dl>
        <h3>例(4)</h3>
        <ul>
            <li ng-repeat="item in hashCollection" ng-class-even="'is-even'" ng-class-odd="'is-odd'">{{item}}</li>
        </ul>
        
        <h2>ngSwitch</h2>
        <select ng-model="selection">
            <option value=""></option>
            <option value="red">赤</option>
            <option value="blue">青</option>
            <option value="green">緑</option>
        </select>
        <p ng-switch="selection">
            <span ng-switch-when="red">赤を</span>
            <span ng-switch-when="blue">青を</span>
            <span ng-switch-when="green">緑を</span>
            <span ng-switch-default>未</span>
            選択
        </p>
        
        <h2>form</h2>
        <form name="form">
            <p ng-show="form.$invalid">不適正な入力あり</p>
            <p>
                テキスト：<input type="text" ng-model="text" name="text" required ng-maxlength="10" ng-pattern="/^[A-Za-z0-9]+$/">
                <span ng-show="form.text.$error.required">未入力</span>
                <span ng-show="form.text.$error.maxlength">文字数オーバー</span>
                <span ng-show="form.text.$error.pattern">英数字以外の文字</span>
            </p>
            <p>
                メールアドレス：<input type="email" ng-model="email" name="email" required>
                <span ng-show="form.email.$error.required">未入力</span>
            </p>
            <p>
                数字：<input type="number" ng-model="number" name="number" min="10" max="20"> ※10〜20
                <span ng-show="form.number.$invalid">範囲外の数字</span>
            </p>
            <p>
                <input type="radio" ng-model="check" value="OK">OK
                <input type="radio" ng-model="check" value="NG">NG<br>
                OKを選択すると↓がチェックされる
            </p>
            <p>
                チェックすると↑でOKが選択される<br>
                <input type="checkbox" ng-model="check" ng-true-value="'OK'" ng-false-value="'NG'"> OK
            </p>
            <p>
                配列タイプ：
                <select ng-model="arraySelect" ng-options="item for item in arrayCollection"></select><br>
                ハッシュタイプ：
                <select ng-model="hashSelect" ng-options="key as item for (key, item) in hashCollection"></select><br>
                グループタイプ：
                <select ng-model="groupSelect" ng-options="key as item.name group by item.group for (key, item) in groupCollection"></select><br>
            </p>
        </form>
    </div>
</div>
```

---
[目次](http://qiita.com/Koizumi-Greenwich/items/3e327e3331b2f728797e)
≪前の記事 6.[カスタムフィルタ](http://qiita.com/Koizumi-Greenwich/items/87e447fd329fd7c00fa6)
≫次の記事 8.[カスタムディレクティブ](http://qiita.com/Koizumi-Greenwich/items/35becc74c18b2e854d09)

---