$compile

概要

HTML文字列の断片、またはテンプレート内のDOMをコンパイルし、 スコープへのリンクとテンプレートで一緒に使用することが出来るテンプレート関数を生成します。

コンパイルとはDOMツリーを辿って、ディレクティブのためにDOM要素をつなぎ合わせる過程の事を示します。

注意: このドキュメントは全てのディレクティブオプションの詳細で専門的なリファレンスになります。 一般的なサンプル付きの初学者向きの内容を参照したければ、ディレクティブのガイドを参照してください。

ディレクティブAPIの詳細

ディレクティブのための多くの異なるオプションが存在します。

異なる部分とは、ファクトリー関数の戻り値に存在します。 ディレクティブのプロパティが定義された"ディレクティブ定義オブジェクト(Directive Definition Object)"(下記を参照)、 またはpostLink関数(他の全てのプロパティはデフォルト値)のどちらかを返すことが可能です。

ベストプラクティス: "ディレクティブ定義オブジェクト"形式を使用することをお勧めします。

下記は、ディレクティブ定義オブジェクトを使用した定義例になります。

var myModule = angular.module(...);

myModule.directive('directiveName', function factory(injectables) {
  var directiveDefinitionObject = {
    priority: 0,
    template: '<div></div>',
    // or
    // function(tElement, tAttrs) { ... },
    // or
    // templateUrl: 'directive.html',
    // or
    // function(tElement, tAttrs) { ... },
    replace: false,
    transclude: false,
    restrict: 'A',
    scope: false,
    controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... },
    require: 'siblingDirectiveName',
    // or
    // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'],
    compile: function compile(tElement, tAttrs, transclude) {
      return {
        pre: function preLink(scope, iElement, iAttrs, controller) { ... },
        post: function postLink(scope, iElement, iAttrs, controller) { ... }
      }
      // or
      // return function postLink( ... ) { ... }
    },
    // or
    // link: {
    //  pre: function preLink(scope, iElement, iAttrs, controller) { ... },
    //  post: function postLink(scope, iElement, iAttrs, controller) { ... }
    // }
    // or
    // link: function postLink( ... ) { ... }
  };
  return directiveDefinitionObject;
});

注意: 指定の無いオプションにはデフォルト値が使用されます。この後の内容でデフォルト値について確認しておいてください。

デフォルト値が使用されるため、上記の例は次のようにシンプルにすることが可能です。

var myModule = angular.module(...);

myModule.directive('directiveName', function factory(injectables) {
  var directiveDefinitionObject = {
    link: function postLink(scope, iElement, iAttrs) { ... }
  };
  return directiveDefinitionObject;
  // or
  // return function postLink(scope, iElement, iAttrs) { ... }
});
ディレクティブ定義オブジェクト(Directive Definition Object)

ディレクティブ定義オブジェクトは、コンパイルに対して指示をする内容を提供します。 その属性は、下記のとおりになります。

priority (優先度)

デフォルト値: 0

複数のディレクティブが1つのDOM要素上に定義されている場合、 ディレクティブが適用される順番が指定されている事が必要になるケースがあります。 priorityは、それらのコンパイル関数が呼び出される前に並び替えを行うのに使用されます。 priorityは数値で定義されます。 より大きな数値のpriorityを持つディレクティブが、先にコンパイルされます。 pre-link関数もまた優先度順に実行されますが、post-link関数は反対の順序で実行されます。 同じ優先度のディレクティブの順序は、不明確になります。

terminal

もし、trueが指定されるとディレクティブの現在のpriority(優先度)が最後として指定されます。 (幾つかのディレクティブは、同じpriorityは不明確としてまだ現在のpriorityの順序で実行されます。(翻訳に自信なし))

scope

もし、trueが指定されると、新しいスコープがこのディレクティブのために作成されます。 もし、同じ要素上の複数のディレクティブが新しいスコープを要求した場合、スコープは1つだけ作成されます。 新しいスコープのルールでは、テンプレートのrootスコープは常に新しいスッコープを取得するため、 テンプレートのrootスコープは適用しません。

もし、{}(ハッシュオブジェクト)が指定されると、新しい"隔離(isolate)"スコープが作成されます。 "隔離(isolate)"スコープは通常のスコープとは異なり、親スコープからプロトタイプ継承を行いません。 これは最利用するコンポーネントを作成する際に便利で、 親スコープのデータを誤って読み込んだり編集してしまうことを防いでくれます。

"隔離(isolate)"スコープは、親スコープから取り出したローカルスコープのプロパティが定義されたオブジェクトハッシュを取得します。 これらのローカルプロパティは、テンプレート値をエイリアスする際に便利です。 ローカルプロパティの定義は、下記を基にしたプロパティのハッシュになります。

  • @ または @attr - DOM属性の値は、ローカルのスコープに紐付けられます。 DOM属性が文字列であるため、この結果は常に文字列になります。 もし、attr名が指定されなければ、属性名はローカルの名前と同じものとみなされます。 <widget my-attr="hello {{name}}">と、 widgetのスコープの定義で{ localName:'@myAttr' }が与えられると、 localNameにはhello {{name}}の値が補完されたものが反映されます。 そのため、name属性が変更されると、それがwidgetスコープ上のlocalNmaeプロパティに反映されます。 nameは親スコープから読み込まれます。(コンポーネントのスコープでは無い)
  • =または=attr - attr属性の値を介して定義された名前のローカルスコープのプロパティと、 親スコープのプロパティ間の双方向紐付けを設定します。 もし、attr名が指定されなければ、属性名はローカルの名前と同じであるとみなされます。 <widget my-attr="parentModel">と{ localModel:'=myAttr' }スコープのwidget定義が与えられると、 widgetスコープのプロパティであるlocalModelには、親スコープ上のparentModelの値が反映されます。 parentModelへの何らかの変更は、localModelに反映され、localModelへの何らかの変更はparentModelに反映されます。 もし、親スコープが存在しなければ、NON_ASSIGNABLE_MODEL_EXPRESSION例外をスローします。 この=?または=?attrを、任意のオプションのフラグとして使用するのは避けてください。
  • &または&attr - 親スコープのコンテキスト内の式を実行する手段を提供します。 もし、attrm名が指定されなければ、属性名はローカル名と同じとみなされます。 <widget my-attr="count = count + value">とスコープのウィジット定義{ localFn:'&myAttr' }が与えられると、 隔離スコープのプロパティlocalFnは、count = count + value式をラップする関数を指し示すようになります。 式を介して隔離スコープから、データを親スコープへ渡すことが望まれることがよくありますが、 ローカル変数名と式をラップしている関数への値のマップを渡すことで、それを行うことが可能です。 例えば、もし式の値(amount)が増加したのであれば、localFn({amount: 22})のようにlocalFnを呼び出すことで、 amount値を指定することが出来ます。
controller

コントローラーのコンスタラクタ関数です。 コントローラーは、pre-linkフェーズと、それが他のディレクティブに共有される前にインスタンス化されます。 (require属性参照) これは、ディレクティブ間で互いに連絡を可能とし、相互の振る舞いを強力にしてくれます。 コントローラーは以降で説明するlocalsで注入可能(且つ括弧表記をサポート)です。

  • $scope - 要素に関連付けられた現在のスコープです。
  • $element - 現在の要素です。
  • $attrs - 要素の現在の属性オブジェクトです。
  • $transclude - transcludeリンク関数は、正しいtransclusionスコープに予め紐付きます。 このスコープは任意の1つ目の引数で上書きすることが可能です。 function([scope], cloneLinkingFn)
require

他のディレクティブを必要とし、そのコントローラーを4つ目の引数としてリンク関数へ注入します。 requireは渡されるディレクティブ(複数可)の名前の文字列を取得します。 もし、配列が使用されると、注入される引数はその順番に対応した配列になります。 もし、指定されたディレクティブが見つからない場合、またはもしディレクティブがコントローラーを持たない場合、 エラーが発生します。 名前には下記の接頭辞が使用されます。

  • (無し) - 現在の要素上で必要とするコントローラーが検索されます。 もし見つからなければ、エラーがスローされます。
  • ? - 必要とするコントローラーの発見を試み、もし見つからなければリンク関数へはnullが渡されます。
  • ^ - 親の要素から検索することで、必要とするコントローラーを発見します。 もし見つからなければ、エラーがスローされます。
  • ?^ - 親の要素から検索することで、必要とするコントローラーの発見を試みます。 もし見つからなければリンク関数へはnullが渡されます。
controlleras
ディレクティブスコープでのコントローラーのエイリアスです。 コントローラーへのエイリアスであるため、ディレクティブのテンプレートで参照することが可能になります。 ディレクティブは、この構成が使用されるスコープの定義を必要とします。 ディレクティブがコンポーネントとして使用されるケースで便利です。
restrict

EACMの各文字は、ディレクティブの宣言スタイルの指定を制限します。 もし、省略されるとデフォルト(属性のみ)が適用されます。

  • E - 要素名: <my-directive></my-directive>
  • A - 属性(デフォルト): <div my-directive="exp"></div>
  • C - クラス: <div class="my-directive: exp;"></div>
  • M - コメント: <!-- directive: my-directive exp -->
template

現在の要素をHTMLのコンテンツに置換します。 置換処理は、古い要素から新しいものへ全ての属性/クラスを移行します。 サンプルについては、ディレクティブのガイドを参照してください。

templateをテンプレートを表す文字列、または2つの引数tElementとtAttrs(以降のcompile関数APIで説明します)を取得し、 テンプレートを表す文字列を返す関数として指定することが出来ます。

templateurl

基本的にtemplateと同じですが、テンプレートを指定したURLから読み込みます。 テンプレートの読み込みは非同期のため、コンパイルとリンクの処理はテンプレートの読み込みが終わるまで待たされます。

templateUrlをURLを表す文字列、または2つの引数tElementとtAttrs(以降のcompile関数APIで説明します)を取得し、 URLを表す文字列を返す関数として指定することが出来ます。 どちらのケースでも、テンプレートURLは$sce.getTrustedResourceUrlを通して渡されます。

replace

テンプレートが挿入されるべき場所を指定します。デフォルトはfalseです。

  • true - テンプレートは現在の要素と置換されます。
  • false - テンプレートは現在の要素のコンテンツを置換します。
transclude

要素のコンテンツをコンパイルし、ディレクティブで利用可能にします。 一般的に、ngTranscludeで使用されます。 トランスクルージョンの利点は、リンク関数が予め正しいスコープへ紐付けられたトランスクルージョン関数を受け取ることです。 通常の構成ではウィジットは隔離スコープを作成しますが、 トランスクルージョンは子では無く、隔離スコープの兄弟になります。 これはウィジットがプライベートな状態を持つことを可能にし、 トランスクルージョンが親(予め隔離された)スコープへ紐付けられます。

  • true - ディレクティブのコンテンツをトランスクルードします。
  • 'element' - 低い優先度(priority)で定義されたディレクティブを含む要素全体をトランスクルードします。
compile
function compile(tElement, tAttrs, transclude) { ... }

compile関数は、テンプレートDOMの変換を扱います。 ほとんどのディレクティブはテンプレートの変換を行わないため、これはそれ程使用されません。 例としてcompile関数が必要となるテンプレートのDOMを変換するディレクティブは、ngRepeatのようなものや、 ngView のように非同期でコンテンツを読み込むようなものです。 compile関数は下記の引数を受け取ります。

  • tElement - テンプレート要素 - ディレクティブが宣言されている要素です。 要素と子要素上でのみテンプレート変換をしても安全です。
  • tAttrs - テンプレート属性 - compile関数で全てのディレクティブ間で共有されるこの要素上で宣言された正規化された属性です。
  • transclude - [非推奨!] トランスクルードのリンク関数: function(scope, cloneLinkingFn)

注意: このテンプレートインスタンスとリンクのインスタンスは、テンプレートが複製されると異なるオブジェクトになるかもしれません。 そのため、compile関数内で複製された全てのDOMノードに適用するDOM変換は危険以外の何物でもありません。 特に、DOMリスナーの登録はcompile関数内では無く、リンク関数内で行うべきです。

注意: transclude関数をcompile関数に渡すことは、例えば外部スコープを正しく理解できないため非推奨です。(翻訳に自信なし) 代わりに、transclude関数はlink関数に渡して使用するようにしてください。

compile関数は、関数またはオブジェクトの戻り値を持つことが可能です。

  • 戻り値が(post-link)関数 - compile関数が空の際に、configオブジェクトのlinkプロパティ介して登録されるリンク関数と等価です。
  • 戻り値がpreとpostプロパティを介して登録された関数(複数可)を持つオブジェクト - リンクのフェーズ中にリンク関数が呼び出されるべきタイミングを制御できます。 後述するpre-linking関数とpost-linking関数について確認しておいてください。
link

このプロパティは、compileプロパティが定義されたなかった場合にのみ使用されます。

function link(scope, iElement, iAttrs, controller, transcludeFn) { ... }

link関数はDOMの更新はもちろんのこと、DOMリスナーの登録も請け負います。 これはテンプレートが複製された後に実行されます。 ディレクティブのロジックの大半が、ここに置かれます。

  • scope - スコープ - 登録された監視(watch)用にディレクティブによって使用されるスコープです。
  • iElement - 要素インスタンス - ディレクティブが使用される要素です。 子は既にリンクされているため、postLink関数内でのみ要素の子を操作しても安全です。
  • iAttrs - 属性インスタンス - この要素上の全てのディレクティブのリンク関数間で共有される、宣言された属性の正規化されたリストです。
  • controller - コントローラーインスタンス - 要素上の少なくとも1つのディレクティブに定義されたコントローラーのインスタンスです。 このコントローラーは全てのディレクティブ間で共有され、 連絡用のチャンネルとしてディレクティブで使用することが出来るようになります。
  • transcludeFn - トランスクルードのリンク関数は、正しいトランスクルージョンのスコープに予め紐付けられます。 スコープは任意の第1引数によって上書きすることが可能です。 これはディレクティブのコントローラーの$transclude引数と同じです。 function([scope], cloneLinkingFn)
pre-linking 関数
子要素がリンクされた後に実行されます。 コンパイラのリンク関数はリンクのために要素への正しい配置に失敗するため、 DOM変換は安全に行うことは出来ません。
post-linking 関数
子要素がリンクされた後に実行されます。 post-linking関数内ではDOM変換は安全に行うことが出来ます。

属性(Attributes)

属性(Attributes) オブジェクト - link()またはcompile()関数内で引数として渡されます。 これには様々な用途があります。

正規化された属性名へのアクセスです。 'ngBind'のようなディレクティブは、'ng:bind'、data-ng-bind、'x-ng-bind'のような多くの方法で表現することが可能です。 属性オブジェクトは、正規化された属性へのアクセスを可能にしてくれます。

Directive間の連絡
全てのディレクティブは、ディレクティブ間での連絡を取り合う事を可能にしてくれる、 属性オブジェクトの同じインスタンスを共有します。
補完のサポート
属性の補完は、他のディレクティブが補完された値を読み込むことを許可する属性オブジェクトを割り当てます。
補完属性の監視
$observeは、補完が含まれる属性の値変更を監視します。(例: src="{{bar}}") これは非常に効率的なだけで無く、リンクフェーズ中の補完はまだ評価されないため、この時の値は未定義(undefined)であるため、 簡単に実際の値を取得する唯一の方法でもあります。
function linkingFn(scope, elm, attrs, ctrl) {
  // 属性の値を取得
  console.log(attrs.ngModel);

  // 属性を変更
  attrs.$set('ngModel', 'new value');

  // 補完された属性の変更を監視
  attrs.$observe('ngModel', function(value) {
    console.log('ngModel has changed value to ' + value);
  });
}

下記は$compileProviderを使用した例になります。

注意: 一般的にディレクティブはmodule.directiveを使用して登録されます。 下記の例は、$compileの動作方法を説明するためのものです。

<!doctype html>
<html ng-app="compile">
  <head>
    <script src="http://code.angularjs.org/1.2.1/angular.min.js"></script>
    <script src="script.js"></script>
  </head>
  <body>
    <div ng-controller="Ctrl">
      <input ng-model="name"> <br>
      <textarea ng-model="html"></textarea> <br>
      <div compile="html"></div>
    </div>
  </body>
</html>
angular.module('compile', [], function($compileProvider) {
  // configure new 'compile' directive by passing a directive
  // factory function. The factory function injects the '$compile'
  $compileProvider.directive('compile', function($compile) {
    // directive factory creates a link function
    return function(scope, element, attrs) {
      scope.$watch(
        function(scope) {
           // watch the 'compile' expression for changes
          return scope.$eval(attrs.compile);
        },
        function(value) {
          // when the 'compile' expression changes
          // assign it into the current DOM
          element.html(value);

          // compile the new DOM and link it to the current
          // scope.
          // NOTE: we only compile .childNodes so that
          // we don't get into infinite loop compiling ourselves
          $compile(element.contents())(scope);
        }
      );
    };
  })
});

function Ctrl($scope) {
  $scope.name = 'Angular';
  $scope.html = 'Hello {{name}}';
}
it('should auto compile', function() {
  expect(element('div[compile]').text()).toBe('Hello Angular');
  input('html').enter('{{name}}!');
  expect(element('div[compile]').text()).toBe('Angular!');
});
<!doctype html>
<html ng-app="compile">
  <head>
    <script src="http://code.angularjs.org/1.2.1/angular.min.js"></script>
<script>angular.module('compile', [], function($compileProvider) {
  // configure new 'compile' directive by passing a directive
  // factory function. The factory function injects the '$compile'
  $compileProvider.directive('compile', function($compile) {
    // directive factory creates a link function
    return function(scope, element, attrs) {
      scope.$watch(
        function(scope) {
           // watch the 'compile' expression for changes
          return scope.$eval(attrs.compile);
        },
        function(value) {
          // when the 'compile' expression changes
          // assign it into the current DOM
          element.html(value);

          // compile the new DOM and link it to the current
          // scope.
          // NOTE: we only compile .childNodes so that
          // we don't get into infinite loop compiling ourselves
          $compile(element.contents())(scope);
        }
      );
    };
  })
});

function Ctrl($scope) {
  $scope.name = 'Angular';
  $scope.html = 'Hello {{name}}';
}
</script>
  </head>
  <body>
    <div ng-controller="Ctrl">
      <input ng-model="name"> <br>
      <textarea ng-model="html"></textarea> <br>
      <div compile="html"></div>
    </div>
  </body>
</html>

使用方法

$compile(element, transclude, maxPriority);
引数 説明
element

型:stringDOM要素

template関数でコンパイルされる要素またはHTML文字列です。

transclude

型:function(angular.Scope[, cloneAttachFn]

ディレクティブで利用可能な関数です。

maxPriority

型:number

与えられたpriority(優先度)より低いディレクティブのみを適用 (root要素(複数可)にのみ影響し、子要素には影響しません)

戻り値 説明
 

型:function(scope[, cloneAttachFn])

スコープにテンプレート(DOM要素/ツリー)を紐付けるのに使用されるlink関数です。

  • scope - 紐付けるスコープを指定します。
  • cloneAttachFn - もし、cloneAttachFnが提供されると、link関数はテンプレートを複製し、 cloneAttachFn関数を呼び出して、呼び出し元に複製された要素を適切な場所でDOM要素に割り当てる事を可能にしてくれます。
    cloneAttachFnは次のようにして呼び出されます。
    cloneAttachFn(clonedElement, scope)
    • clonedElement - コンパイラへ渡された元の要素の複製です。
    • scope - リンク関数が動作するカレントのスコープです。

リンク関数の呼び出すと、テンプレートの要素を返します。 これは渡された元の要素、またはcloneAttachFnが提供されていれば複製された要素のどちらかになります。

リンクされた後は、ビューは通常Angularによって自動的に実行される$digestが呼ばれるまで、更新されることはありません。

  • もし、リンク関数へテンプレートを複製することを問い合わせないのであれば、 コンパイラへそれらを送信してこの参照周りを保持する前にDOM要素を作成します。

    var element = $compile('<p>{{total}}</p>')(scope);
    
  • もし、一方で要素の複製が必要な場合、 元の例のビューの参照は、複製された元のテンプレートというよりも、 むしろ複製を指し示すことをしません。(翻訳に自信なし) この場合、cloneAttachFnを介して複製にアクセスすることが可能です。

    var templateHTML = angular.element('<p>{{total}}</p>'),
        scope = ....;
    
    var clonedElement = $compile(templateHTML)(scope, function(clonedElement, scope) {
      //正しい場所にDOM要素の複製を割り当てます
    });
    
    //以降、`clone`を介して複製されたDOMを参照します
    

    コンパイルの動作方法の情報については、 AngularのデベロッパーガイドのHTMLコンパイラを参照してください。

 Back to top

© 2017 Google
Licensed under the Creative Commons Attribution License 3.0.

このページは、ページトップのリンク先のAngularJS公式ドキュメント内のページを翻訳した内容を基に構成されています。 下記の項目を確認し、必要に応じて公式のドキュメントをご確認ください。 もし、誤訳などの間違いを見つけましたら、 @tomofまで教えていただければ幸いです。

  • AngularJSの更新頻度が高いため、元のコンテンツと比べてドキュメントの情報が古くなっている可能性があります。
  • "訳注:"などの断わりを入れた上で、日本人向けの情報やより分かり易くするための追記を行っている事があります。