LoginSignup
1
1

More than 5 years have passed since last update.

Angular 1.x でイベントハンドラを設定できる directive を作る($parse の使い方)

Posted at

$parse を使って ngClick のように $event などの引数付きでイベントハンドラを設定できる directive を作ります。

<a ng-click="vm.doSomething($event)">Do Something</a>

ただ関数を実行するだけなら scope.$eval(attrs.onSomething) すれば OK です。ngClick$event のように scope にはない引数を渡すにはどうすればいいでしょうか?ngClick の実装を見てみましょう。

ngClick の実装

ngClick など DOM のイベントそのままの directive は angular/angular.jssrc/ng/directive/ngEventDirs.js で定義されています。ngClick は概ね以下のような実装になっています(本当は複数のイベントに対応する directive を一気に定義するようになっていますが、簡明のため ngClick だけになるよう書き換えています)。

var ngClickDirective = function($parse) {
  return {
    compile: function($element, attr) {
      var fn = $parse(attr.ngClick, null, true);
      return function(scope, element) {
        element.on('click', function(event) {
          var callback = function() {
            fn(scope, {$event:event});
          };
          scope.$apply(callback);
        });
      };
    }
  };
};

$parse なるものが使われていますね。

$parse と locals

$parse の公式ドキュメントを読むと、expression を関数に変換するとあります。ドキュメント中の例では object のプロパティに get/set をしています。

var getter = $parse('user.name');
var setter = getter.assign;
var context = {user:{name:'angular'}};
var locals = {user:{name:'local'}};

expect(getter(context)).toEqual('angular');
setter(context, 'newValue');
expect(context.user.name).toEqual('newValue');
expect(getter(context, locals)).toEqual('local');

$parse() の結果が getter で、その assign プロパティが setter ですね。getter には locals というものを使うことで、scope に加えてその expression 中だで使う context を指定できます。これにより scope を汚すことなく、そのイベントハンドラだけで使える変数を用意することができます。ちなみに scope と同じプロパティがある場合は、locals が優先されるようです。

上の ngClick では {$event:event} という locals が使われていました。これを真似すれば、イベントハンドラに独自の引数を渡せますね。

$parse の隠れた引数

公式ドキュメントでは $parse の引数は一つだけです。しかし ngClick を見ると、もう 2 つあることがわかります。

2 つ目の引数は interceptorFn とあるので、expression の評価結果をいじるための関数と思われます。

3 つ目の引数は expensiveChecks というものだそうで、$parse に渡す expression が window にアクセスしないかを、戻り値の関数実行時にチェックするようです。コメントには、イベントハンドラは頻繁に実行されないので expensive でも構わず実行すると書いてあります。

Expression Sandbox によれば、これはセキュリティのためではなく window を使った設計をできないようにするためだそうです。

scope で window を露出させることは通常ないのでチェックしていないが、このフラグを true にするとチェックする模様。

セキュリティ目的というわけでもないので気にしなくても良さそうですが、ngClick とかでつかってるので真似しておくといいかもしれません。

自作する

仕組みは分かったので、後は ngClick を真似するだけですね。

  1. compile で expression を $parse() して結果の関数をとっておく(link でもいいけど)。
  2. イベントハンドラを実行するときは fn(scope, locals) のように scope と(必要なら)locals を渡して実行する。
1
1
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
1
1