Angularディレクティブ-コンパイル、コントローラー、プリリンク、ポストリンクをいつ、どのように使用するか[終了]


451

Angularディレクティブを記述する場合、次の関数のいずれかを使用して、DOMの動作、コンテンツ、およびディレクティブが宣言されている要素の外観を操作できます。

  • コンパイル
  • コントローラ
  • プレリンク
  • ポストリンク

どの関数を使用するべきかについては、いくつかの混乱があるようです。この質問の対象は次のとおりです。

ディレクティブの基本

関数の性質、すべきこと、すべきでないこと

関連する質問:


27
何なんだ?
haimlit 2014

2
@Ian See:演算子のオーバーロード。基本的に、これはコミュニティーWikiを対象としています。関連する質問への回答の多くが部分的であり、全体像を提供していません。
Izhaki 14

8
これは素晴らしいコンテンツですが、ここではすべてQ&A形式で保管してください。おそらく、これを複数の個別の質問に分けて、タグwikiからそれらにリンクしたいですか?
フレキソ

57
この投稿はトピック外でブログ形式ですが、Angularディレクティブの詳細な説明を提供するのに最も役立ちました。管理者はこの投稿を削除しないでください!
解釈14年

12
正直なところ、私は元のドキュメントを気にしません。Stackoverflowの投稿またはブログでは通常、数秒以内に進みますが、元のドキュメントを理解しようとして髪を引き裂く15〜30分です。
デビッド

回答:


168

ディレクティブ関数はどの順序で実行されますか?

単一のディレクティブの場合

次のplunkに基づいて、次のHTMLマークアップを検討してください。

<body>
    <div log='some-div'></div>
</body>

次のディレクティブ宣言で:

myApp.directive('log', function() {

    return {
        controller: function( $scope, $element, $attrs, $transclude ) {
            console.log( $attrs.log + ' (controller)' );
        },
        compile: function compile( tElement, tAttributes ) {
            console.log( tAttributes.log + ' (compile)'  );
            return {
                pre: function preLink( scope, element, attributes ) {
                    console.log( attributes.log + ' (pre-link)'  );
                },
                post: function postLink( scope, element, attributes ) {
                    console.log( attributes.log + ' (post-link)'  );
                }
            };
         }
     };  

});

コンソール出力は次のようになります。

some-div (compile)
some-div (controller)
some-div (pre-link)
some-div (post-link)

私たちは、それを見ることができるcompile最初の、そして実行されcontroller、その後pre-link、最後ですpost-link

ネストされたディレクティブの場合

注:以下は、リンク関数で子をレンダリングするディレクティブには適用されません。かなりの数のAngularディレクティブがこれを行います(ngIf、ngRepeat、またはを使用したディレクティブなどtransclude)。これらのディレクティブは、子ディレクティブが呼び出さlinkれる前にネイティブに呼び出される関数を持ちcompileます。

多くの場合、元のHTMLマークアップはネストされた要素で構成され、それぞれに独自のディレクティブがあります。次のマークアップのように(plunkを参照):

<body>
    <div log='parent'>
        <div log='..first-child'></div>
        <div log='..second-child'></div>
    </div>
</body>

コンソール出力は次のようになります。

// The compile phase
parent (compile)
..first-child (compile)
..second-child (compile)

// The link phase   
parent (controller)
parent (pre-link)
..first-child (controller)
..first-child (pre-link)
..first-child (post-link)
..second-child (controller)
..second-child (pre-link)
..second-child (post-link)
parent (post-link)

ここでは、コンパイルフェーズとリンクフェーズの2つのフェーズを区別できます。

コンパイル段階

DOMが読み込まれると、Angularはコンパイルフェーズを開始します。コンパイルフェーズでは、マークアップを上から下に移動し、compileすべてのディレクティブを呼び出します。グラフィック的には、次のように表現できます。

子供向けのコンパイルループを示す画像

この段階で、コンパイル関数が取得するテンプレートはソーステンプレート(インスタンステンプレートではない)であることを言及することはおそらく重要です。

リンクフェーズ

多くの場合、DOMインスタンスは、ソーステンプレートがDOMにレンダリングされた結果ですが、によって作成されるng-repeatか、その場で導入されます。

ディレクティブを持つ要素の新しいインスタンスがDOMにレンダリングされるたびに、リンクフェーズが開始されます。

このフェーズでは、Angularはcontroller、を呼び出し、pre-link子を反復しpost-link、次のようにすべてのディレクティブを呼び出します。

リンクフェーズの手順を示す図


5
@lzhakiフローチャートは良さそうです。グラフ作成ツールの名前を共有しますか?:)
マーリン2014

1
@merlin私はOmniGraffleを使用しました(ただし、イラストレーターやインクスケープを使用できたでしょう-速度以外に、この図に関する限り、OmniGraffleが他のグラフ作成ツールよりも優れていることはありません)。
Izhaki 2014

2
:Anantのplunker @ので、ここで新しいものだ消えplnkr.co/edit/kZZks8HN0iFIY8ZaKJkA?p=previewオープンはログステートメントを参照するにはJSコンソール

ng-repeatが子のディレクティブに使用されている場合、なぜこれは当てはまりませんか??? plunkを
p

あなたのplunkがNGリピート下ディレクティブで子を持っていない@Luckylooke(すなわち、どのように繰り返されているのは、ディレクティブとテンプレートであることが希望した場合、あなたは彼らのコンパイルのみngのリピートのリンク後に呼び出されることを参照してくださいね。。
イザキ

90

これらの関数呼び出しの間で他に何が起こりますか?

各種指令機能は、二つ呼ばれる他の角度の関数内から実行される$compile(ディレクティブのがcompile実行される)と呼ばれる内部機能nodeLinkFn(指示者controllerpreLink及びpostLink実行されます)。ディレクティブ関数が呼び出される前と後に、角度関数内でさまざまなことが起こります。おそらく最も顕著なのは、子供の再帰です。次の簡略図は、コンパイルおよびリンクフェーズ内の主要な手順を示しています。

Angularのコンパイルとリンクのフェーズを示す図

これらの手順を示すために、次のHTMLマークアップを使用してみましょう。

<div ng-repeat="i in [0,1,2]">
    <my-element>
        <div>Inner content</div>
    </my-element>
</div>

次のディレクティブで:

myApp.directive( 'myElement', function() {
    return {
        restrict:   'EA',
        transclude: true,
        template:   '<div>{{label}}<div ng-transclude></div></div>'
    }
});

コンパイル

compileAPIのルックスがとても好きです。

compile: function compile( tElement, tAttributes ) { ... }

多くの場合、パラメータの前にプレフィックスを付けてt、提供される要素と属性がインスタンスの属性ではなく、ソーステンプレートの属性であることを示します。

compileトランスクルードされたコンテンツ(存在する場合)の呼び出しの前に削除され、テンプレートがマークアップに適用されます。したがって、compile関数に提供される要素は次のようになります。

<my-element>
    <div>
        "{{label}}"
        <div ng-transclude></div>
    </div>
</my-element>

この時点では、変換されたコンテンツは再挿入されないことに注意してください。

ディレクティブのの呼び出しに続いて.compile、Angularは、ディレクティブによって導入されたばかりの子要素(テンプレート要素など)を含むすべての子要素をトラバースします。

インスタンスの作成

この例では、上記のソーステンプレートの3つのインスタンスが(によってng-repeat)作成されます。したがって、次のシーケンスは、インスタンスごとに1回ずつ、3回実行されます。

コントローラ

controllerAPIが含まれます。

controller: function( $scope, $element, $attrs, $transclude ) { ... }

リンクフェーズに入り、経由$compileで返されるリンク関数にスコープが提供されるようになりました。

最初に、リンク関数は、要求された場合、子スコープ(scope: true)または分離スコープ(scope: {...})を作成します。

次に、コントローラーが実行され、インスタンス要素のスコープが提供されます。

プレリンク

pre-linkAPIのルックスがとても好きです。

function preLink( scope, element, attributes, controller ) { ... }

ディレクティブの呼び出し.controller.preLink関数の間では、実質的に何も起こりません。Angularは、それぞれがどのように使用されるべきかについての推奨をまだ提供しています。

.preLink呼び出しに続いて、リンク関数は各子要素をトラバースします-正しいリンク関数を呼び出し、それに現在のスコープ(子要素の親スコープとして機能します)をアタッチします。

ポストリンク

post-linkAPIはpre-link関数のAPIに似ています。

function postLink( scope, element, attributes, controller ) { ... }

おそらく、ディレクティブの.postLink関数が呼び出されると、すべての子の.postLink関数を含む、そのすべての子要素のリンクプロセスが完了したことに注目する価値があります。

これは、.postLink呼び出されるまでに、子供が「生きている」準備ができていることを意味します。これも:

  • データバインディング
  • トランスクルージョンが適用されました
  • 付属のスコープ

したがって、この段階でのテンプレートは次のようになります。

<my-element>
    <div class="ng-binding">
        "{{label}}"
        <div ng-transclude>                
            <div class="ng-scope">Inner content</div>
        </div>
    </div>
</my-element>

3
この図面をどのように作成しましたか?
Royi Namir、2015

6
@RoyiNamirオムニグラフル
Izhaki 2015年

43

さまざまな関数を宣言する方法は?

コンパイル、コントローラー、プレリンク、ポストリンク

4つすべての関数を使用する場合、ディレクティブは次の形式に従います。

myApp.directive( 'myDirective', function () {
    return {
        restrict: 'EA',
        controller: function( $scope, $element, $attrs, $transclude ) {
            // Controller code goes here.
        },
        compile: function compile( tElement, tAttributes, transcludeFn ) {
            // Compile code goes here.
            return {
                pre: function preLink( scope, element, attributes, controller, transcludeFn ) {
                    // Pre-link code goes here
                },
                post: function postLink( scope, element, attributes, controller, transcludeFn ) {
                    // Post-link code goes here
                }
            };
        }
    };  
});

compileはリンク前関数とリンク後関数の両方を含むオブジェクトを返すことに注意してください。Angularの専門用語では、コンパイル関数はテンプレート関数を返します。

コンパイル、コントローラー、ポストリンク

場合はpre-link必要ありません、コンパイル機能は、単純にそうように、代わりに定義オブジェクトの後のリンク機能を返すことができます。

myApp.directive( 'myDirective', function () {
    return {
        restrict: 'EA',
        controller: function( $scope, $element, $attrs, $transclude ) {
            // Controller code goes here.
        },
        compile: function compile( tElement, tAttributes, transcludeFn ) {
            // Compile code goes here.
            return function postLink( scope, element, attributes, controller, transcludeFn ) {
                    // Post-link code goes here                 
            };
        }
    };  
});

compile(post)linkメソッドが定義された後で、メソッドを追加したい場合があります。これには、以下を使用できます。

myApp.directive( 'myDirective', function () {
    return {
        restrict: 'EA',
        controller: function( $scope, $element, $attrs, $transclude ) {
            // Controller code goes here.
        },
        compile: function compile( tElement, tAttributes, transcludeFn ) {
            // Compile code goes here.

            return this.link;
        },
        link: function( scope, element, attributes, controller, transcludeFn ) {
            // Post-link code goes here
        }

    };  
});

コントローラー&ポストリンク

コンパイル関数が必要ない場合は、宣言を完全にスキップしてlink、ディレクティブの設定オブジェクトのプロパティでポストリンク関数を提供できます。

myApp.directive( 'myDirective', function () {
    return {
        restrict: 'EA',
        controller: function( $scope, $element, $attrs, $transclude ) {
            // Controller code goes here.
        },
        link: function postLink( scope, element, attributes, controller, transcludeFn ) {
                // Post-link code goes here                 
        },          
    };  
});

コントローラーなし

上記のいずれの例でも、controller必要がなければ、関数を削除できます。たとえば、post-link関数のみが必要な場合は、次のように使用できます。

myApp.directive( 'myDirective', function () {
    return {
        restrict: 'EA',
        link: function postLink( scope, element, attributes, controller, transcludeFn ) {
                // Post-link code goes here                 
        },          
    };  
});

31

ソーステンプレートインスタンステンプレートの違いは何ですか?

AngularがDOM操作を許可するという事実は、コンパイルプロセスへの入力マークアップが出力と異なる場合があることを意味します。特に、一部の入力マークアップはng-repeat、DOMにレンダリングされる前に(のように)数回複製される場合があります。

角度の用語は少し矛盾していますが、2つのタイプのマークアップを区別しています。

  • ソーステンプレート -必要に応じて、複製するマークアップ。複製した場合、このマークアップはDOMにレンダリングされません。
  • インスタンステンプレート -DOMにレンダリングされる実際のマークアップ。クローンが関係する場合、各インスタンスはクローンになります。

次のマークアップはこれを示しています。

<div ng-repeat="i in [0,1,2]">
    <my-directive>{{i}}</my-directive>
</div>

ソースhtmlが定義します

    <my-directive>{{i}}</my-directive>

ソーステンプレートとして機能します。

ただし、ng-repeatディレクティブ内でラップされているため、このソーステンプレートは複製されます(この例では3回)。これらのクローンはインスタンステンプレートであり、それぞれがDOMに表示され、関連するスコープにバインドされます。


23

コンパイル機能

各ディレクティブのcompile関数は、Angularブートストラップ時に一度だけ呼び出されます。

公式には、これはスコープやデータバインディングを含まない(ソースの)テンプレート操作を実行する場所です。

これは主に、最適化の目的で行われます。次のマークアップを検討してください。

<tr ng-repeat="raw in raws">
    <my-raw></my-raw>
</tr>

<my-raw>ディレクティブは、DOMのマークアップの特定のセットをレンダリングします。したがって、次のいずれかを行うことができます。

  • 許可ng-repeat(ソース・テンプレートを複製する<my-raw>)、次いで(外部各インスタンステンプレートのマークアップ変更compile機能)。
  • compile関数内の)目的のマークアップを含むようにソーステンプレートを変更し、それng-repeatを複製できるようにします。

rawsコレクションに1000個のアイテムがある場合、後者のオプションは前者のオプションよりも高速になる可能性があります。

行う:

  • マークアップを操作して、インスタンス(クローン)のテンプレートとして機能するようにします。

しない

  • イベントハンドラーをアタッチします。
  • 子要素を調べます。
  • 属性の観測を設定します。
  • スコープに時計を設定します。

20

コントローラー機能

各ディレクティブのcontroller関数は、新しい関連要素がインスタンス化されるたびに呼び出されます。

公式には、controller機能は次のいずれかです。

  • コントローラ間で共有できるコントローラロジック(メソッド)を定義します。
  • スコープ変数を開始します。

繰り返しになりますが、ディレクティブに分離されたスコープが含まれている場合、親スコープから継承するディレクティブ内のプロパティはまだ利用できないことに注意してください。

行う:

  • コントローラロジックを定義する
  • スコープ変数を開始する

しない:

  • 子要素を検査します(まだレンダリングされていない、スコープにバインドされているなど)。

Controller withinディレクティブは、スコープを初期化するのに最適な場所です。私はそれを発見するのに苦労しました。
jsbisht

1
コントローラーは「スコープを開始する」ことはせず、すでに独立して開始されているスコープにのみアクセスします。
ドミトリザイツェフ2015年

@DmitriZaitsev細部へのこだわり。テキストを修正しました。
Izhaki 2015年

19

ポストリンク機能

ときpost-link結合、トランスクルーなど-関数が呼び出され、以前のすべての手順が行われました

これは通常、レンダリングされたDOMをさらに操作する場所です。

行う:

  • DOM(レンダリングされてインスタンス化された)要素を操作します。
  • イベントハンドラーをアタッチします。
  • 子要素を調べます。
  • 属性の観測を設定します。
  • スコープに時計を設定します。

9
リンク機能(プリリンクまたはポストリンクなし)を使用している場合は、ポストリンクと同等であることを知っておくと便利です。
Asaf David

15

プリリンク機能

各ディレクティブのpre-link関数は、新しい関連要素がインスタンス化されるたびに呼び出されます。

コンパイル順序のセクションで前述したように、pre-link関数は親から子post-linkと呼ばれ、関数はと呼ばれchild-then-parentます。

このpre-link関数はめったに使用されませんが、特別なシナリオで役立ちます。たとえば、子コントローラーがそれ自体を親コントローラーに登録するが、登録はあるparent-then-child方法で行われる必要がある場合(ngModelControllerこの方法で行われます)。

しない:

  • 子要素を検査します(まだレンダリングされていない、スコープにバインドされているなど)。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.