Angular JSディレクティブのレンダリング後コールバックはありますか?


139

次のように、テンプレートに要素を追加するためのテンプレートをプルするディレクティブを取得しました。

# CoffeeScript
.directive 'dashboardTable', ->
  controller: lineItemIndexCtrl
  templateUrl: "<%= asset_path('angular/templates/line_items/dashboard_rows.html') %>"
  (scope, element, attrs) ->
    element.parent('table#line_items').dataTable()
    console.log 'Just to make sure this is run'

# HTML
<table id="line_items">
    <tbody dashboard-table>
    </tbody>
</table>

DataTablesというjQueryプラグインも使用しています。その一般的な使用法は次のとおりです:$( 'table#some_id')。dataTable()。JSONデータをdataTable()呼び出しに渡してテーブルデータを提供できます。または、データをページ上に既に持っていて、残りの処理を実行できます。私は後者を行っており、行は既にHTMLページ上にあります。

しかし問題は、DOMの準備が整った後でtable#line_itemsのdataTable()を呼び出さなければならないことです。上記のディレクティブは、テンプレートがディレクティブの要素に追加される前にdataTable()メソッドを呼び出します。追加後に関数を呼び出す方法はありますか?

ご協力ありがとうございました!

アンディの答えの後の更新1:

linkメソッドがページ上にすべてある場合にのみ呼び出されるようにしたいので、少しテストのためにディレクティブを変更しました。

# CoffeeScript
#angular.module(...)
.directive 'dashboardTable', ->
    {
      link: (scope,element,attrs) -> 
        console.log 'Just to make sure this gets run'
        element.find('#sayboo').html('boo')

      controller: lineItemIndexCtrl
      template: "<div id='sayboo'></div>"

    }

そして、div#saybooに実際に「boo」が表示されます。

次に、jqueryデータテーブル呼び出しを試します

.directive 'dashboardTable',  ->
    {
      link: (scope,element,attrs) -> 
        console.log 'Just to make sure this gets run'
        element.parent('table').dataTable() # NEW LINE

      controller: lineItemIndexCtrl
      templateUrl: "<%= asset_path('angular/templates/line_items/dashboard_rows.html') %>"
    }

運が悪い

次に、タイムアウトを追加してみます。

.directive 'dashboardTable', ($timeout) ->
    {
      link: (scope,element,attrs) -> 
        console.log 'Just to make sure this gets run'
        $timeout -> # NEW LINE
          element.parent('table').dataTable()
        ,5000
      controller: lineItemIndexCtrl
      templateUrl: "<%= asset_path('angular/templates/line_items/dashboard_rows.html') %>"
    }

そしてそれはうまくいきます。では、タイマー以外のバージョンのコードで何が問題になっているのでしょうか。


1
@adardesignいいえ、やったことはありません。タイマーを使わなければなりませんでした。なんらかの理由で、実際にはコールバックはここではコールバックではありません。私は11列と100の行を持つテーブルを持っているので、データバインディングに使用するのに適しているのは当然のことです。しかし、$( 'table')。datatable()と同じくらい簡単なjquery Datatablesプラグインも使用する必要があります。ディレクティブを使用するか、すべての行を含む単純なjsonオブジェクトを作成し、ng-repeatを使用して反復すると、テーブルのhtml要素がレンダリングされた後で$()。datatable()を実行できません。 $( 'tr')。length> 3(ヘッダー/フッターのb / c)かどうかを確認するには
Nik So

2
@adardesignはい、すべてのコンパイルメソッド、メソッドpostLink / preLinkを含むオブジェクトを返すコンパイルメソッド、関数(つまりリンク関数)だけを返すコンパイルメソッド、リンクメソッド(私が知る限り、コンパイルメソッドなし)を試しました。リンクメソッドを返すコンパイルメソッドがある場合、リンク関数は無視されます)。動作しないので、古き良き$ timeoutに依存する必要があります。コールバックが本当にコールバックのように機能していることがわかった場合、または単にコールバックのように機能することがわかったときに、この投稿を更新します
Nik So

回答:


215

2番目のパラメーター「delay」が指定されていない場合、デフォルトの動作では、DOMのレンダリングが完了した後に関数が実行されます。したがって、setTimeoutの代わりに、$ timeoutを使用します。

$timeout(function () {
    //DOM has finished rendering
});

8
なぜそれがドキュメントで説明されていないのですか?
Gaui、2014

23
あなたが正しい、私がそれを簡単にしようとしたので、私の答えは少し誤解を招くものです。完全な答えは、この効果はAngularの結果ではなく、ブラウザの結果であるということです。$timeout(fn)最終的にsetTimeout(fn, 0)、Javascriptの実行を中断し、ブラウザがそのJavaScriptの実行を続行する前にコンテンツをレンダリングできるようにする効果のある呼び出し。
国会

7
ブラウザは、「JavaScript実行」や「DOMレンダリング」などの特定のタスクを別々にキューイングしていると考え、レンダリング後に、現在実行中の「JavaScript実行」をキューの後ろにプッシュするsetTimeout(fn、0)は何ですか。 。
議会

2
@GabLeRoux yup、それは$ timeoutが実行後に$ scope。$ apply()を呼び出すという追加の利点があることを除いて同じ効果があります。_.defer()を使用すると、myFunctionがスコープの変数を変更する場合、手動で呼び出す必要があります。
国会

2
私はこれがpage1でng-repeatが要素の束をレンダリングするのに役に立たないシナリオを持っています、それから私はpage2に行き、それからpage1に戻り、ng-repeat要素の親を取得しようとします...間違った高さを返します。1000msのようにタイムアウトした場合、それは機能します。
yodalr 2015年

14

私は同じ問題を抱えていましたが、答えは本当にノーだと思います。Miškoのコメントグループでの議論を参照してください。

Angularは、DOMを操作するために行うすべての関数呼び出しが完了したことを追跡できますが、これらの関数は非同期ロジックをトリガーし、戻り後もDOMを更新し続ける可能性があるため、Angularはそれについて知ることができませんでした。Angularが与えるコールバックは時々機能するかもしれませんが、信頼するのは安全ではありません。

私たちは、あなたがしたように、setTimeoutを使ってこれを発見的に解決しました。

(誰もが私に同意するわけではないことに注意してください-上記のリンクのコメントを読んで、あなたの考えを見てください。)


7

テンプレートを配置した後に実行されるpostLinkとも呼ばれる「リンク」関数を使用できます。

app.directive('myDirective', function() {
  return {
    link: function(scope, elm, attrs) { /*I run after template is put in */ },
    template: '<b>Hello</b>'
  }
});

ディレクティブを作成する予定がある場合は、こちらをお読みください。http//docs.angularjs.org/guide/directive


こんにちはアンディ、答えてくれてありがとう。リンク機能を試しましたが、コーディングしたとおりに再試行してもかまいません。私は最後の1.5日間をそのディレクティブページで読み上げました。Angularのサイトの例も見てください。今すぐコードを試します。
Nik So

ああ、私は今あなたがリンクをしようとしていたが、あなたはそれを間違っていたのを見ます。関数を返すだけの場合は、リンクされていると見なされます。オブジェクトを返す場合は、キーを「リンク」としてオブジェクトを返す必要があります。コンパイル関数からリンク関数を返すこともできます。
Andrew Joslin

こんにちはアンディ、私の結果を取り戻しました。基本的にここであなたの答えを本当にしたので、私は正気を失っていました。私のアップデートをご覧ください
Nik So

次のように試してください:<table id = "bob"> <tbody dashboard-table = "#bob"> </ tbody> </ table>次に、リンクで$(attrs.dashboardTable).dataTable()を実行して、正しく選択されていることを確認してください。または、あなたはすでにそれを試したと思います...リンクが機能していないかどうかは本当にわかりません。
アンドリュージョスリン

これは私のために働きました、私は私の要件のためにテンプレートのdom postレンダリング内の要素を移動したかったです、リンク機能でそれを行いました。
ありがとう

7

私の答えはデータテーブルとは関係ありませんが、それはDOM操作の問題に対処します。たとえば、内容が非同期で更新される要素で使用されるディレクティブのjQueryプラグインの初期化です。

タイムアウトを実装する代わりに、コンテンツの変更(または追加の外部トリガー)をリッスンするウォッチを追加することもできます。

私の場合、ng-repeatが実行されて内部DOMが作成されたら、jQueryプラグインを初期化するためにこの回避策を使用しました。ここに私がやった方法があります...

HTML:

<div my-directive my-directive-watch="!!myContent">{{myContent}}</div>

JS:

app.directive('myDirective', [ function(){
    return {
        restrict : 'A',
        scope : {
            myDirectiveWatch : '='
        },
        compile : function(){
            return {
                post : function(scope, element, attributes){

                    scope.$watch('myDirectiveWatch', function(newVal, oldVal){
                        if (newVal !== oldVal) {
                            // Do stuff ...
                        }
                    });

                }
            }
        }
    }
}]);

注: my-directive-watch属性でmyContent変数をboolにキャストする代わりに、そこで任意の式を想像できます。

注:上記の例のようにスコープを分離することは、要素ごとに1回しか実行できません。同じ要素で複数のディレクティブを使用してこれを実行しようとすると、$ compile:multidirエラーが発生します-参照:https : //docs.angularjs.org / error / $ compile / multidir


7

この質問に答えるのが遅いかもしれません。しかし、それでも誰かが私の答えから利益を得るかもしれません。

同様の問題があり、私の場合、ディレクティブは変更できません。それはライブラリであり、ライブラリのコードを変更することはお勧めできません。したがって、私が行ったのは、変数を使用してページの読み込みを待機し、html内でng-ifを使用して特定の要素のレンダリングを待機することでした。

私のコントローラーでは:

$scope.render=false;

//this will fire after load the the page

angular.element(document).ready(function() {
    $scope.render=true;
});

私のhtml(私の場合、htmlコンポーネントはキャンバスです)

<canvas ng-if="render"> </canvas>

3

同じ問題がありましたが、Angular + DataTable fnDrawCallback+ 行グループ + $ compiledネストされたディレクティブを使用しています。$ timeoutをfnDrawCallback関数に配置して、ページ分割のレンダリングを修正しました。

例の前に、row_groupingソースに基づいて:

var myDrawCallback = function myDrawCallbackFn(oSettings){
  var nTrs = $('table#result>tbody>tr');
  for(var i=0; i<nTrs.length; i++){
     //1. group rows per row_grouping example
     //2. $compile html templates to hook datatable into Angular lifecycle
  }
}

例の後:

var myDrawCallback = function myDrawCallbackFn(oSettings){
  var nTrs = $('table#result>tbody>tr');
  $timeout(function requiredRenderTimeoutDelay(){
    for(var i=0; i<nTrs.length; i++){
       //1. group rows per row_grouping example
       //2. $compile html templates to hook datatable into Angular lifecycle
    }
  ,50); //end $timeout
}

短いタイムアウト遅延でさえ、Angularがコンパイル済みのAngularディレクティブをレンダリングできるようにするのに十分でした。


気になるだけですが、列数の多いかなり大きなテーブルがありますか?dataTable()がチョークを呼び出さないようにするために、迷惑なミリ秒(> 100)が必要であることがわかりました
Nik So

この問題は、2行から150行を超える結果セットのDataTableページナビゲーションで発生したことがわかりました。したがって、いいえ-テーブルのサイズが問題だったとは思いませんが、おそらくDataTableは、それらのミリ秒の一部をかき消すのに十分なレンダリングオーバーヘッドを追加しました。私の焦点は、行のグループ化を最小限のAngularJS統合でDataTableで機能させることでした。
JJ Zabkar 2013

2

私のために働いた解決策はどれも、タイムアウトの使用を受け入れません。これは、postLink中に動的に作成されたテンプレートを使用していたためです。

ただし、タイムアウトにより「0」のタイムアウトが発生する可能性があることに注意してください。タイムアウトにより、ブラウザのキューに呼び出される関数が追加されます。これは、角度レンダリングエンジンが既にキューにあるため、これが発生した後に発生します。

これを参照してください:http : //blog.brunoscopelliti.com/run-a-directive-after-the-dom-has-finished-rendering


0

これは、浅いレンダリングの後にアクションをプログラムするためのディレクティブです。浅いとは、要素そのものがレンダリングされた後に評価されそのコンテンツがいつレンダリングされるかとは無関係であることを意味します。したがって、レンダリング後のアクションを実行するサブ要素が必要な場合は、そこで使用することを検討する必要があります。

define(['angular'], function (angular) {
  'use strict';
  return angular.module('app.common.after-render', [])
    .directive('afterRender', [ '$timeout', function($timeout) {
    var def = {
        restrict : 'A', 
        terminal : true,
        transclude : false,
        link : function(scope, element, attrs) {
            if (attrs) { scope.$eval(attrs.afterRender) }
            scope.$emit('onAfterRender')
        }
    };
    return def;
    }]);
});

その後、あなたは行うことができます:

<div after-render></div>

または次のような便利な式を使用します:

<div after-render="$emit='onAfterThisConcreteThingRendered'"></div>


これはコンテンツがレンダリングされた後ではありません。この時点で要素<div after-render> {{blah}} </ div>内に式があった場合、式はまだ評価されていません。divの内容は、リンク関数内にまだ{{blah}}です。したがって、技術的には、コンテンツがレンダリングされる前にイベントを発生させます。
Edward Olamisan 2015年

これはレンダリング後の浅いアクションであり、私はそれが深いと主張したことはありません
Sebastian Sastre

0

私はこれを次のディレクティブで動作させました:

app.directive('datatableSetup', function () {
    return { link: function (scope, elm, attrs) { elm.dataTable(); } }
});

そしてHTMLでは:

<table class="table table-hover dataTable dataTable-columnfilter " datatable-setup="">

上記の方法で問題が解決しない場合は、トラブルシューティングを行ってください。

1)「datatableSetup」は「datatable-setup」と同等であることに注意してください。Angularは形式をキャメルケースに変更します。

2)アプリがディレクティブの前に定義されていることを確認します。たとえば、単純なアプリの定義とディレクティブ。

var app = angular.module('app', []);
app.directive('datatableSetup', function () {
    return { link: function (scope, elm, attrs) { elm.dataTable(); } }
});

0

ロード順序が予想できないという事実に続いて、簡単なソリューションを使用できます。

ディレクティブと「ディレクティブのユーザー」の関係を見てみましょう。通常、ディレクティブのユーザーは、データをディレクティブに提供するか、ディレクティブが提供する機能(関数)を使用します。一方、ディレクティブは、そのスコープで定義されるいくつかの変数を想定しています。

すべてのプレーヤーがそれらのアクションを実行する前に、すべてのアクション要件が満たされていることを確認できれば、すべてが順調であるはずです。

そして今指令:

app.directive('aDirective', function () {
    return {
        scope: {
            input: '=',
            control: '='
        },
        link: function (scope, element) {
            function functionThatNeedsInput(){
                //use scope.input here
            }
            if ( scope.input){ //We already have input 
                functionThatNeedsInput();
            } else {
                scope.control.init = functionThatNeedsInput;
            }
          }

        };
})

そして今ディレクティブhtmlのユーザー

<a-directive control="control" input="input"></a-directive>

ディレクティブを使用するコンポーネントのコントローラーのどこかに:

$scope.control = {};
...
$scope.input = 'some data could be async';
if ( $scope.control.functionThatNeedsInput){
    $scope.control.functionThatNeedsInput();
}

それだけです。オーバーヘッドはたくさんありますが、$ timeoutを失う可能性があります。また、ディレクティブがインスタンス化されるときに存在する制御変数に依存しているため、ディレクティブを使用するコンポーネントはディレクティブの前にインスタンス化されると想定しています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.