4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2014-07-18

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

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

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

コード

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つを使います。

コード

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を呼び出すための汎用ライブラリ

4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?