web-dev-qa-db-ja.com

AngularJS:AngularJSがテンプレートをレンダリングした後に追加のコードを実行する方法

DOMにAngularテンプレートがあります。私のコントローラがサービスから新しいデータを取得するとき、それは$スコープのモデルを更新し、そしてテンプレートを再レンダリングします。これまでのところすべて良かった。

問題は、テンプレートが再レンダリングされてDOMに入った後(この場合はjQueryプラグイン)にも追加の作業を行う必要があることです。

AfterRenderのような聴くべきイベントがあるはずですが、私はそのようなことを見つけることができません。たぶん指令が行く方法であるかもしれません、しかしそれはあまりにも早く発砲するように見えました。

これが私の問題の概要を説明するjsFiddleです。 Fiddle-AngularIssue

== UPDATE ==

有用なコメントに基づいて、私はそれに応じてDOM操作を処理するディレクティブに切り替え、ディレクティブの内側にモデル$ watchを実装しました。しかし、私はまだ同じ基本的な問題を抱えています。 $ watchイベント内のコードは、テンプレートがコンパイルされてDOMに挿入される前に発生するため、jqueryプラグインは常に空のテーブルを評価しています。

興味深いことに、非同期呼び出しを削除しても全体がうまく機能するので、正しい方向へのステップです。

これらの変更を反映するために私が更新したFiddleは次のとおりです。 http://jsfiddle.net/uNREn/12/

178
gorebash

この記事は古いですが、私はあなたのコードを次のように変更します。

scope.$watch("assignments", function (value) {//I change here
  var val = value || null;            
  if (val)
    element.dataTable({"bDestroy": true});
  });
}

jsfiddle を参照してください。

お役に立てば幸いです。

39
dipold

まず、レンダリングを台無しにするのに適切な場所はディレクティブです。私のアドバイスは、このようなディレクティブによってjQueryプラグインを操作するDOMをラップすることです。

私は同じ問題を抱えていて、この断片を思い付きました。 $watchおよび$evalAsyncを使用して、ng-repeatなどのディレクティブが解決され、{{ value }}などのテンプレートがレンダリングされた後にコードが実行されるようにします。

app.directive('name', function() {
    return {
        link: function($scope, element, attrs) {
            // Trigger when number of children changes,
            // including by directives like ng-repeat
            var watch = $scope.$watch(function() {
                return element.children().length;
            }, function() {
                // Wait for templates to render
                $scope.$evalAsync(function() {
                    // Finally, directives are evaluated
                    // and templates are renderer here
                    var children = element.children();
                    console.log(children);
                });
            });
        },
    };
});

これがあなたが何らかの闘争を防ぐのを手伝うことができると思います。

63
danijar

Miskoのアドバイスに従い、もし非同期操作が欲しいのなら、$ timeout()の代わりに(これはうまくいかない)

$timeout(function () { $scope.assignmentsLoaded(data); }, 1000);

$ evalAsync()を使用します(これは動作します)。

$scope.$evalAsync(function() { $scope.assignmentsLoaded(data); } );

フィドル 。また、データの変更が機能することを示すために、$ scope.assignmentsを変更してデータ/モデルへの変更をシミュレートする「データ行の削除」リンクも追加しました。

「概念の概要」ページの ランタイム のセクションでは、現在のスタックフレームの外側でブラウザがレンダリングする前に何か発生させる必要がある場合にevalAsyncを使用する必要があることを説明します。 (ここで推測しますが、「現在のスタックフレーム」にはおそらくAngular DOMの更新が含まれます。)ブラウザのレンダリング後に何か発生させる必要がある場合は$ timeoutを使用してください。

しかし、すでにご存知のように、ここでは非同期操作は必要ないと思います。

34
Mark Rajcok

私は最も簡単な(安くて元気な)解決策は単にレンダリングされた最後の要素の最後にng-show = "someFunctionThatAlwaysReturnsZeroOrNothing()"で空のスパンを追加することです。この関数は、span要素を表示する必要があるかどうかを確認するときに実行されます。この関数の他のコードを実行してください。

私はこれが物事を行うための最もエレガントな方法ではないことを認識していますが、それは私のために動作します...

アニメーションが始まったときにローディングインジケータを削除する必要があったのとは少し逆になったものの、モバイルデバイスでは表示されるアニメーションよりもずっと早くAngularが初期化され、ローディングインジケータとしてNGクロークを使用するだけでは不十分でした。実際のデータが表示される前に削除されました。この場合、最初にレンダリングされた要素にreturn 0関数を追加し、その関数内でロードインジケータを非表示にするvarを反転しました。 (もちろん、この機能によって引き起こされるローディングインジケータにng-hideを追加しました。

26
fuzion9

私はあなたが$ evalAsync http://docs.angularjs.org/api/ng.$ro​​otScope.Scope#$evalAsync を探していると思います

7
Misko Hevery

最後に私は解決策を見つけた、私は私のコレクションを更新するためにRESTサービスを使っていた。データ化可能なjqueryを変換するためのコードは以下のとおりです。

$scope.$watchCollection( 'conferences', function( old, nuew ) {
        if( old === nuew ) return;
        $( '#dataTablex' ).dataTable().fnDestroy();
        $timeout(function () {
                $( '#dataTablex' ).dataTable();
        });
    });
4
Sergio Ceron

私はこれをかなり頻繁にしなければなりませんでした。私はディレクティブがあり、モデルのものが完全にDOMにロードされた後にいくつかのjqueryのものをする必要があります。だから私はリンクに私のロジックを入れて:ディレクティブの関数とsetTimeout(関数(){.....}、1)でコードをラップします。 setTimoutはDOMが読み込まれた後に起動します。1ミリ秒は、DOMが読み込まれてからコードが実行されるまでの最短時間です。これは私にはうまくいくようですが、そのテンプレートで使用されるディレクティブがjqueryものを実行してDOM要素にアクセスできるように、テンプレートのロードが完了したらAngularイベントを発生させたいです。お役に立てれば。

3
mgrimmenator

リンク関数でコードを実行するディレクティブを作成することもできます。

そのstackoverflow reply を参照してください。

2
Anthony O.

$ scope。$ evalAsync()も$ timeout(fn、0)も私にとっては確実には機能しませんでした。

私は二つを組み合わせる必要がありました。指示を出し、また、デフォルト値よりも高い優先順位を設定しています。これがそのためのディレクティブです(注:依存関係を注入するためにngInjectを使用します)。

app.directive('postrenderAction', postrenderAction);

/* @ngInject */
function postrenderAction($timeout) {
    // ### Directive Interface
    // Defines base properties for the directive.
    var directive = {
        restrict: 'A',
        priority: 101,
        link: link
    };
    return directive;

    // ### Link Function
    // Provides functionality for the directive during the DOM building/data binding stage.
    function link(scope, element, attrs) {
        $timeout(function() {
            scope.$evalAsync(attrs.postrenderAction);
        }, 0);
    }
}

指令を呼び出すには、次のようにします。

<div postrender-action="functionToRun()"></div>

Ng-repeatの実行が完了した後でそれを呼び出したい場合は、ng-repeatとng-if = "$ last"に空のスパンを追加しました。

<li ng-repeat="item in list">
    <!-- Do stuff with list -->
    ...

    <!-- Fire function after the last element is rendered -->
    <span ng-if="$last" postrender-action="$ctrl.postRender()"></span>
</li>
2
Xchai

私はかなり簡単な解決策を考え出しました。それが正しいやり方かどうかはわかりませんが、実用的な意味ではうまくいきます。レンダリングしたいものを直接見てみましょう。たとえば、いくつかのng-repeatを含むディレクティブでは、段落のテキストの長さ(他のものもあります)に注意してください。指令は次のようになります。

.directive('myDirective', [function () {
    'use strict';
    return {

        link: function (scope, element, attrs) {
            scope.$watch(function(){
               var whole_p_length = 0;
               var ps = element.find('p');
                for (var i=0;i<ps.length;i++){
                    if (ps[i].innerHTML == undefined){
                        continue
                    }
                    whole_p_length+= ps[i].innerHTML.length;
                }
                //it could be this too:  whole_p_length = element[0].innerHTML.length; but my test showed that the above method is a bit faster
                console.log(whole_p_length);
                return whole_p_length;
            }, function (value) {   
                //Code you want to be run after rendering changes
            });
        }
}]);

NOTEは、実際には レンダリングの変更後 または完全なレンダリング後にコードが実行されることを意味します。しかし、ほとんどの場合、レンダリングの変更が発生したときはいつでも状況に対処できると思います。また、コードの実行のみを 一度 レンダリングの完了後に実行したい場合は、このpsの長さ(またはその他の測定値)をモデルと比較することも考えられます。 私はこれについての考え/コメントに感謝します。

1
Ehsan88

サービスを更新して新しいビュー(ページ)にリダイレクトしてからサービスが更新される前にディレクティブが読み込まれるようなシナリオでは、$ rootScopeを使用できます。

見る

<service-history log="log" data-ng-repeat="log in requiedData"></service-history>

コントローラ

app.controller("MyController",['$scope','$rootScope', function($scope, $rootScope) {

   $scope.$on('$viewContentLoaded', function () {
       SomeSerive.getHistory().then(function(data) {
           $scope.requiedData = data;
           $rootScope.$broadcast("history-updation");
       });
  });

}]);

指令

app.directive("serviceHistory", function() {
    return {
        restrict: 'E',
        replace: true,
        scope: {
           log: '='
        },
        link: function($scope, element, attrs) {
            function updateHistory() {
               if(log) {
                   //do something
               }
            }
            $rootScope.$on("history-updation", updateHistory);
        }
   };
});
0
Sudharshan

angular-ui utils の 'jQuery Passthrough'モジュールを使うことができます。私はjQuery touchのカルーセルプラグインをWebサービスから非同期で取得してng-repeatでレンダリングするいくつかの画像にうまくバインドできました。

0
Michael Kork.