angularjsのコンパイルとリンク機能の違いは何ですか


208

誰かが簡単な言葉で説明できますか?

ドキュメントは少し鈍いようです。どちらを使用するかについての本質と全体像がわかりません。2つを対比する例は素晴らしいでしょう。



回答:


217
  • コンパイル関数- テンプレート DOM操作(つまり、tElement =テンプレート要素の操作)に使用します。したがって、ディレクティブに関連付けられたテンプレートのすべてのDOMクローンに適用される操作です。

  • リンク関数-DOM リスナー(つまり、インスタンススコープの$ watch式)およびインスタンス DOM操作(つまり、iElement =個々のインスタンス要素の操作)の登録に使用します。
    テンプレートが複製された後に実行されます。たとえば、<li ng-repeat ...>内では、その特定の<li>要素の<li>テンプレート(tElement)が(iElementに)複製された後で、リンク関数が実行されます。
    $ watch()を使用すると、ディレクティブにインスタンススコープのプロパティの変更を通知できます(インスタンススコープは各インスタンスに関連付けられています)。これにより、ディレクティブは更新されたインスタンス値をDOMにレンダリングします-インスタンススコープからコンテンツをDOM。

DOM変換は、コンパイル関数またはリンク関数、あるいはその両方で実行できることに注意してください。

ほとんどのディレクティブは特定のDOM要素インスタンス(およびそのインスタンススコープ)のみを処理するため、ほとんどのディレクティブはリンク関数のみを必要とします。

どちらを使用するかを決定するための1つの方法:コンパイル関数がscope引数を受け取らないことを考慮して ください。(私は意図的に、トランスクルードされたスコープを受け取るトランスクルードリンク関数の引数を無視しています-これはめったに使用されません。)したがって、コンパイル関数は、(インスタンス)スコープを必要とする、実行したいことは何もできません-できます。 'モデル/インスタンススコープのプロパティを$ watchしないでください。インスタンススコープ情報を使用してDOMを操作したり、インスタンススコープで定義された関数を呼び出したりすることはできません。

ただし、コンパイル関数(リンク関数など)は属性にアクセスできます。したがって、DOM操作でインスタンススコープが不要な場合は、コンパイル関数を使用できます。ここだのみの理由で、コンパイル機能を使用するディレクティブの。それは属性を調べますが、その仕事をするのにインスタンススコープは必要ありません。

ここだも唯一のコンパイル機能を使用するディレクティブの。ディレクティブはテンプレートDOMを変換する必要があるだけなので、コンパイル関数を使用できます。

どちらを使用するかを決定するのに役立つ別の方法:リンク関数で「要素」パラメーターを使用しない場合、おそらくリンク関数は必要ありません。

ほとんどのディレクティブにはリンク関数があるため、例を示すつもりはありません。それらは非常に見つけやすいはずです。

コンパイル関数とリンク関数(またはリンク前関数とリンク後関数)が必要な場合、「コンパイル」属性が定義されていると「リンク」属性が無視されるため、コンパイル関数はリンク関数を返す必要があることに注意してください。

こちらもご覧ください


5
コンパイルとリンクの最良の説明。
Nexus23、2015年

1
あなたが言うときif you don't use the "element" parameter in the link function, then you probably don't need a link function.、あなたは代わりに「要素」の「範囲」を意味するのですか?
Jason Larke、

69

これで数日間壁に頭をぶつけて、もう少し説明が必要だと思います。

基本的に、ドキュメントは分離が主にパフォーマンスの向上であると述べています。サブエレメント自体がコンパイルされる前にDOMを変更する必要がある場合、コンパイルフェーズが主に使用されることを繰り返します。

ここでは、混乱を招くような用語を強調します。

コンパイラSERVICE($ compile)は、DOMを処理し、ディレクティブでコードのさまざまなビットを実行する角度メカニズムです。

コンパイルFUNCTIONは、コンパイラSERVICE($ compile)によって特定の時間に実行されるディレクティブ内の1ビットのコードです。

コンパイル機能に関する注意事項:

  1. ROOT要素(ディレクティブが影響する要素)は変更できません。これは、DOMの外部レベルから既にコンパイルされているためです(コンパイルサービスはその要素のディレクティブをすでにスキャンしています)。

  2. (ネストされた)要素に他のディレクティブを追加する場合は、次のいずれかを行います。

    1. コンパイル段階でそれらを追加する必要があります。

    2. コンパイルサービスをリンクフェーズに挿入し、要素を手動でコンパイルする必要があります。しかし、何かを2回コンパイルすることに注意してください!

$ compileのネストと明示的な呼び出しがどのように機能するかを確認することも役立つため、http://jsbin.com/imUPAMoV/1/editでそれを表示するための遊び場を作成しました。基本的には、ステップをconsole.logに記録するだけです。

そのビンに表示される結果をここに示します。次のようにネストされたカスタムディレクティブtpおよびspのDOMの場合:

<tp>
   <sp>
   </sp>
</tp>

AngularコンパイルSERVICEは以下を呼び出します:

tp compile
sp compile
tp pre-link
sp pre-link
sp post-link
tp post-link

jsbinコードには、3番目のディレクティブ(up)でコンパイルサービスを明示的に呼び出すtp post-link FUNCTIONもあります。これにより、最後に3つのステップすべてが実行されます。

ここで、コンパイルとリンクを使用してさまざまなことを行う方法を示すために、いくつかのシナリオについて説明します。

シナリオ1:マクロとしての指令

属性から派生できるテンプレート内の何かにディレクティブ(たとえばng-show)を動的に追加したい。

次を指すtemplateUrlがあるとします。

<div><span><input type="text"></span><div>

そしてあなたはカスタムディレクティブが必要です:

<my-field model="state" name="address"></my-field>

これにより、DOMは次のようになります。

<div><span ng-show="state.visible.address"><input ng-model="state.fields.address" ...>

基本的に、ディレクティブが解釈できる一貫性のあるモデル構造を用意することで、ボイラープレートを削減したいとします。つまり、マクロが必要です。

これは、すべてのDOM操作を属性だけから知っていることに基づいて行うことができるため、コンパイルフェーズの優れた用途です。jQueryを使用して属性を追加するだけです。

compile: function(tele, tattr) {
   var span = jQuery(tele).find('span').first();
   span.attr('ng-show', tattr.model + ".visible." + tattr.name);
   ...
   return { 
     pre: function() { },
     post: function() {}
   };
}

操作のシーケンスは次のようになります(これは、前述のjsbinで確認できます)。

  1. コンパイルサービスはmy-fieldを検出します
  2. DOMを更新するディレクティブでコンパイルFUNCTIONを呼び出します。
  3. 次に、コンパイルサービスは結果のDOMに移動し、コンパイルします(再帰的に)。
  4. 次に、コンパイルサービスはプレリンクトップダウンを呼び出します
  5. 次に、コンパイルサービスはポストリンクのBOTTOM UPを呼び出すため、my-fieldのリンク関数は、内部ノードがリンクされた後に呼び出されます。

上記の例では、ディレクティブのすべての作業がコンパイルFUNCTIONで行われたため、リンクは必要ありません。

いつでも、ディレクティブ内のコードは、コンパイラSERVICEに追加の要素で実行するように要求できます。

これは、コンパイルサービスを挿入した場合、リンク関数でまったく同じことができることを意味します。

directive('d', function($compile) {
  return {
    // REMEMBER, link is called AFTER nested elements have been compiled and linked!
    link: function(scope, iele, iattr) {
      var span = jQuery(iele).find('span').first();
      span.attr('ng-show', iattr.model + ".visible." + iattr.name);
      // CAREFUL! If span had directives on it before
      // you will cause them to be processed again:
      $compile(span)(scope);
    }
});

$ compile SERVICEに渡す要素が元々ディレクティブを含まないことが確実である場合(たとえば、定義したテンプレートからのものであるか、angular.element()で作成したばかりの場合)、最終結果はかなり多くなります。以前と同じ(ただし、いくつかの作業を繰り返す場合があります)。ただし、要素に他のディレクティブが含まれている場合は、それらを再度処理するだけで、あらゆる種類の不安定な動作(イベントと監視の二重登録など)が発生する可能性があります。

したがって、コンパイルフェーズは、マクロスタイルの作業にははるかに優れた選択肢です。

シナリオ2:スコープデータによるDOM構成

これは、上記の例に従っています。DOMの操作中にスコープにアクセスする必要があるとします。まあ、その場合、コンパイルセクションはスコープが利用可能になる前に発生するため、役に立たない。

したがって、検証を使用して入力を取り出したいが、サーバー側ORMクラス(DRY)から検証をエクスポートし、それらを自動適用して、それらの検証に適切なクライアント側UIを生成したいとします。

あなたのモデルは押すかもしれません:

scope.metadata = {
  validations: {
     address: [ {
       pattern: '^[0-9]',
       message: "Address must begin with a number"
     },
     { maxlength: 100,
       message: "Address too long"
     } ]
  }
};
scope.state = {
  address: '123 Fern Dr'
};

そしてあなたはディレクティブが必要かもしれません:

<form name="theForm">
  <my-field model="state" metadata="metadata" name="address">
</form>

適切なディレクティブとdivを自動インクルードして、さまざまな検証エラーを表示します。

<form name="theForm">
  <div>
    <input ng-model="state.address" type="text">
    <div ng-show="theForm.address.$error.pattern">Address must begin with a number</input>
...

この場合、必ずスコープにアクセスする必要があります(検証が格納される場所であるため)、追加を手動でコンパイルする必要があります。ここでも、ダブルコンパイルしないように注意してください。(補足として、含まれているフォームタグに名前を設定する必要があります(ここではtheFormを想定しています)、iElement.parent()。controller( 'form')。$ nameとリンクしてアクセスできます) 。

この場合、コンパイル関数を作成しても意味がありません。リンクはあなたが本当に望んでいるものです。手順は次のとおりです。

  1. 角度指令がまったくないテンプレートを定義します。
  2. さまざまな属性を追加するリンク関数を定義する
  3. トップレベルの要素で許可する可能性のある角度ディレクティブ(my-fieldディレクティブ)を削除します。それらはすでに処理されており、これはそれらが二重処理されないようにする方法です。
  4. 最上位の要素でコンパイルサービスを呼び出して終了します。

そのようです:

angular.module('app', []).
directive('my-field', function($compile) {
  return {
    link: function(scope, iele, iattr) {
      // jquery additions via attr()
      // remove ng attr from top-level iele (to avoid duplicate processing)
      $compile(iele)(scope); // will pick up additions
    }
  };
});

もちろん、ネストされた要素を1つずつコンパイルして、最上位の要素を再度コンパイルするときにngディレクティブの重複処理について心配する必要をなくすことができます。

このシナリオに関する最後の1つのメモ:検証の定義をサーバーからプッシュすることを暗示し、私の例では、スコープ内のデータとしてそれらを示しました。読者がREST APIからそのデータをプルする必要がある場合の対処方法を理解するための演習として残しておきます(ヒント:遅延コンパイル)。

シナリオ3:リンクを介した双方向のデータバインディング

もちろん、リンクの最も一般的な使用法は、watch / applyを介して双方向のデータバインディングをフックすることです。ほとんどのディレクティブはこのカテゴリに分類されるため、他の場所で適切にカバーされています。


2
素晴らしくてクールな答え!
Nexus23、2014

それらを二重にコンパイルせずにネストされた要素を追加するにはどうすればよいですか?
Art713、2015年

50

ドキュメントから:

コンパイラ

コンパイラーは、属性を探してDOMをトラバースする角度付きサービスです。コンパイルプロセスは2つのフェーズに分かれます。

  1. コンパイル: DOMをトラバースし、すべてのディレクティブを収集します。結果はリンク関数です。

  2. リンク:ディレクティブとスコープを組み合わせて、ライブビューを作成します。スコープモデルの変更はビューに反映され、ビューに対するユーザー操作はスコープモデルに反映されます。スコープモデルを単一の真の情報源にする。

そのようないくつかのディレクティブng-repeatは、コレクション内のアイテムごとに1回DOM要素を複製します。コンパイルおよびリンクフェーズを使用すると、クローンテンプレートを1回だけコンパイルしてから、各クローンインスタンスに対して1回リンクするだけでよいため、パフォーマンスが向上します。

したがって、少なくとも一部のケースでは、2つのフェーズは最適化として別々に存在します。


@UmurKontacıから

DOM変換を行う場合は、そうする必要がありますcompile。動作の変更であるいくつかの機能を追加したい場合、それはにあるはずlinkです。


46
DOM変換を行うcompile場合は、動作の変更であるいくつかの機能を追加する場合、それはにある必要がありlinkます。
UmurKontacı2012

4
上記のコメントに+1。これは私がこれまでに見つけた最も簡潔な説明です。ここで見つけたチュートリアルと一致しています
ベニーボッテマ2013年

18

これは指令に関するMiskoの講演からです。http://youtu.be/WqmeI5fZcho?t=16m23s

コンパイラー機能は、テンプレートで機能するもの、および例えば、クラスにテンプレートを追加するなどしてテンプレート自体を変更できるものと考えてください。ただし、リンク関数はスコープにアクセスでき、特定のテンプレートのインスタンス化ごとに1回実行されるリンク関数であるため、2つを実際にバインドするのはリンク関数です。したがって、コンパイル関数の内部に配置できるのは、すべてのインスタンスに共通するものだけです。


10

スレッドに少し遅れました。しかし、将来の読者のために:

Angular JSでのコンパイルとリンクについて非常に優れた方法で説明している次のビデオを見つけました。

https://www.youtube.com/watch?v=bjFqSyddCeA

ここにすべてのコンテンツをコピー/入力するのは楽しいことではありません。私はビデオからいくつかのスクリーンショットを撮りました、それはコンパイルとリンク段階のすべての段階を説明します:

Angular JSでのコンパイルとリンク

Angular JSでのコンパイルとリンク-ネストされたディレクティブ

2番目のスクリーンショットは少し混乱しています。しかし、ステップ番号付けに従うと、非常に簡単です。

最初のサイクル:すべてのディレクティブで「コンパイル」が最初に実行されます。
2番目のサイクル:「コントローラー」と「プレリンク」が実行されます(次々に)3番目のサイクル:「ポストリンク」が逆の順序で実行されます(最も内側から開始)

以下は、上記を示すコードです。

var app = angular.module( 'app'、[]);

app.controller( 'msg'、['$ scope'、function($ scope){

}]);

app.directive( 'message'、function($ interpolate){
    戻る{

        コンパイル:function(tElement、tAttributes){ 
            console.log(tAttributes.text + "-In compile ..");
            返す{

                pre:function(scope、iElement、iAttributes、controller){
                    console.log(iAttributes.text + "-In pre ..");
                }、

                post:function(scope、iElement、iAttributes、controller){
                    console.log(iAttributes.text + "-In Post ..");
                }

            }
        }、

        コントローラー:function($ scope、$ element、$ attrs){
            console.log($ attrs.text + "-In controller ..");
        }、

    }
});
<body ng-app="app">
<div ng-controller="msg">
    <div message text="first">
        <div message text="..second">
            <div message text="....third">

            </div>              
        </div>  
    </div>
</div>

更新:

同じビデオのパート2は、こちらから入手できます。https//www.youtube.com/watch?v = 1M3LZ1cu7rw このビデオでは、DOMを変更し、Angular JSのコンパイルおよびリンクプロセス中にイベントを処理する方法について、簡単な例で詳しく説明しています。


使用compileし、postそれが上で修正されていることを前に、DOMを変更するためにtemplate、ベンダーの指示から一部。
-jedi

6

2つのフェーズ:コンパイルとリンク

コンパイル:

ディレクティブ(要素/属性/クラス/コメント)を探してDOMツリーをトラバースします。ディレクティブをコンパイルするたびに、テンプレートが変更されるか、まだコンパイルされていない内容が変更される場合があります。ディレクティブが一致すると、リンク関数が返されます。これは、後のフェーズで要素をリンクするために使用されます。コンパイルフェーズの最後に、コンパイルされたディレクティブとそれに対応するリンク関数のリストがあります。

リンク:

要素がリンクされると、DOMツリーはDOMツリー内の分岐点で壊れ、コンテンツはテンプレートのコンパイルされた(およびリンクされた)インスタンスに置き換えられます。置き換えられた元のコンテンツは破棄されるか、転置の場合はテンプレートに再度リンクされます。トランスクルージョンを使用すると、2つのピースがリンクされます(チェーンのようなもので、テンプレートピースが中央にあります)。リンク関数が呼び出されたとき、テンプレートはすでにスコープにバインドされており、要素の子として追加されています。リンク機能は、DOMをさらに操作し、変更リスナーをセットアップする機会です。


3

この質問は古いので、役立つ可能性がある短い要約を作成したいと思います。

  • すべてのディレクティブインスタンスに対して1回呼び出されるコンパイル
  • コンパイルの主な目的は、リンク(および場合によっては事前/事後)の関数/オブジェクトを返す/作成することです。ディレクティブのインスタンス間で共有されるものを初期化することもできます。
  • 私の意見では、「リンク」はこの機能のわかりにくい名前です。「プリレンダー」がいいです。
  • linkはディレクティブインスタンスごとに呼び出され、その目的はDOMでのディレクティブのレンダリングを準備することです。

1
提案名の1つのプラス:「事前レンダリング」
Hailong Cao
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.