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