angularの$parse, $interpolate, $compile 位は把握しよう
はじめに
https://t.co/43GvPaLHo0
angularさんについてこれ空で分かる程度には分かっててほしいと思ったりした。
— po (@podhmo) 2016, 1月 12
$parse, $interpolate, $compile これらはどれも文字列のような別の表現を解釈してjs objectを返すような機能を持つ関数達。 内部で使われている事が多いがどのようなものか把握しておくとangular.js自体のコードを読むのが捗るかもしれない。
前提
後々あげられているコードを試すには、 angularをnode.js上で実行する方法 を把握しておく必要がある。コレ使います。
各種API
各関数の説明を加える前に雑なmoduleを作っておく。このモジュールは後で使う。
angular.module("app", []); var inj = angular.injector(["ng", "app"]); var scope = { person: {name: "foo", "age": 20} }; var locals = { where: "anywhere" }; var inj = angular.injector(["ng", "app"]);
angularで定義したmoduleはinjectorを使って生成することができる。新しいmodule objectを生成するのであって取り出すわけではない。
$parse
$parseはある環境で渡された文字列を評価する関数を返す関数。環境としてscopeを取る。
var $parse = inj.get("$parse"); console.log($parse("person.name")(scope)); // => foo
実はもう一つ引数を渡せる、値が見つからなかった場合にはもう一つの方の引数の値から探す。
console.log($parse("where")(scope, locals)); // => anywhere
$eval
$evalは$parseの短縮版。ある$scopeに結びついた環境での値の取得というように考えれば良い。 実際、内部の実装自体はのような簡単なもの。(thisはscope)
$eval: function(expr, locals) { return $parse(expr)(this, locals); },
使い方も変わらない。scopeはdefaultで用意されている$rootScopeを借りる。
var $eval = inj.get("$rootScope").$eval; console.log($eval.call(scope, "person.name")); // => foo console.log($eval("where", locals)); // => anywhere
$interpolate
interpolate = 補間する、挿入するという意味。そのままplaceholder部分を置き換える機能。 $interpolateもある文字列表現を受け取ってjs objectを返す関数であることは変わらない。$parseの亜種だと思えば良い。 template内でのbindingなどに使われている。
var $interpolate = inj.get("$interpolate"); console.log($interpolate("{{person.name}}({{person.age}})")(scope)); // foo(20)
$compile
全部やる。全部。実際のところ、ng-appのdirectiveをつけた時には、諸々の変更の後にこれを呼びまくっている。興味のある人はbootstrap()という関数を覗いてみると良い。
今回はちょっと長いけれど、自前のdirectiveを作ってそれの動作を確認する
angular.module("d", []) .directive("cell", [function(){ return { restrict: "E", controller: function Controller(){ }, scope: {}, bindToController: { person: "&" }, controllerAs: "c", template: '<dl><dt>name</dt><dd>{{c.person().name}}</dd></dl>' }; }]); var injector = angular.injector(["ng", "d"]);
以下の様にして使う。scopeはかわりに$rootScopeを使う。
var $compile = injector.get("$compile"); var $rootScope = injector.get("$rootScope"); $rootScope.s = {person: {name: "bar", age: 10}}; var compiled = $compile("<cell person=\"s.person\"></cell>")($rootScope); console.log(angular.element(compiled).html());
このコードに対して以下のような出力を期待するかもしれない。
<dl><dt>name</dt><dd class="ng-binding">bar</dd></dl>
実際は異なる。実際の出力は以下のようなもの。
<dl><dt>name</dt><dd class="ng-binding">{{c.person().name}}</dd></dl>
何故かと言うとdigest loopがまわっていないため。scope.$apply()を呼び出してやると期待した出力になる。
$rootScope.$apply(); console.log(angular.element(compiled).html()); // => <dl><dt>name</dt><dd class="ng-binding">bar</dd></dl>