angularの$parse, $interpolate, $compile 位は把握しよう

はじめに

$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>

gist

https://gist.github.com/podhmo/18da6d82219817ac44d3