AngularJSで、属性として与えられた任意のAngular式を双方向バインドする

属性として与えられたAngular式を、ビューとバインドするとき、通常は、ngModelが使われます。しかし、
ngModelでは一つしか属性をバインドすることができません。これを自前で行うためには、$parseというサービスを使います。このための基本パターンを紹介します。

また、比較のため、ngModelを使って同等のことをするパターンも示します。ちなみに、ngModelを使うとCSSのクラスが自動で設定されたり、formetter, parserで他のディレクティブと連携するといったことが行えますが、これらはngModelの機能なので自前でバインドした場合、これらの機能は使えません。

# ngModelを使わない双方向バインド

## コード

```js
module.directive('directiveName', ['$parse', function ($parse) {
    function testAttrHandler(accessor, element, scope) {
        var field = {
            event: '{eventName}',
            setter: function (element, val) {
                //TO DO
            },
            getter: function (element, val) {
                //TO DO
            }
        };
        /* model -> view */
        scope.$watch(accessor, function (val) {
            field.setter(element, val);
        }, true);
        /* view -> model */
        element.on(field.event, function (event, ui) {
            scope.$apply(function () {
                accessor.assign(scope, field.getter(element, event, ui));
            });
        });
    }
    var handlerMap = {
        testAttr: testAttrHandler
    };
    return {
        compile: function (element, attr) {
            var accessors = {};
            angular.forEach(attr, function (exp, attrName) {
                if (!handlerMap[attrName]) {
                    return;
                }
                accessors[attrName] = $parse(exp);
            });
            return function prelink(scope, element, attr) {
                angular.forEach(accessors, function (accessor, attrName) {
                    handlerMap[attrName](accessor, element, scope);
                });
            };
        }
    };
}]);
```

## $parseの使い方

\$parseにAngular式を与えるとaccsessorを返します。accsessor(scope)で値を取得し、accsessor.assign(scope, value)で値をセットします。

\$parseはscopeに依存しないので、prelinkの外側、compileの内側で行うのが良いです。このようにすることで、ng-repeatのように一度のcompileで複数のlinkが行われる時、一度だけ\$parseが呼ばれるようになります。

## $watchとaccessor

watch関数は、文字列でwatch対象を受け取るのが普通ですが、関数の受け取ることもできます。この関数のインタフェースは、\$parseが返すaccessorと互換性があるので、\$parseの戻り値をそのまま\$watchに渡せば監視ができます。

文字列を与えても内部的には\$parseが使われているだけなので、せっかくaccessorを作ったのだから、それを\$watchに与える方が、無駄な\$parseが発生せず、パフォーマンス的にも優れています。また、コードも見やすくすっきりします。

# ngModelを使う場合

ngModelを使う場合は、同等のことをするのに、ngModel.\$render, ngModel.\$modelValue, ngModel.\$setViewValueの3つを使います。

## コード

```js
module.directive('directiveName', [function () {
    function ngModelHandler(ngModel, element, scope,) {
        var field = {
            event: '{eventName}',
            setter: function (element, val) {
                //TO DO
            },
            getter: function (element, val) {
                //TO DO
            }
        };
        /* model -> view */
        ngModel.$render = function () {
            field.setter(element, ngModel.$modelValue);
        };
        /* view -> model */
        element.on(field.event, function (event, ui) {
            ngModel.$setViewValue(field.getter(element, event, ui));
            if (!scope.$$phase) {
                scope.$apply();
            }
        });
    }
    return {
        require: '?ngModel',
        compile: function (element, attr) {
            return function prelink(scope, element, attr, ngModel) {
                ngModelHandler(ngModel, element, scope);
            };
        }
    };
}]);
```

# 応用

これらをさらに汎用化することで、以下のようなライブラリを作りました。

[AngularJSからjQueryを呼び出すための汎用ライブラリ](http://qiita.com/norami_dream/items/a8c30b7e191767626e5d)