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


182

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

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

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

ここに私の問題を概説するjsFiddleがあります:Fiddle-AngularIssue

==更新==

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

興味深いことに、非同期呼び出しを削除すると、すべてが正常に機能するので、これは正しい方向への一歩です。

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


これは私が持っていた質問に似ているようです。stackoverflow.com/questions/11444494/…。多分そこに何かが役立つかもしれません。
dnc253 2012

回答:


39

この投稿は古いですが、コードを次のように変更します。

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

jsfiddleを参照してください。

お役に立てば幸いです


ありがとう、それはとても役に立ちます。これは、コントローラーからのdom操作を必要とせず、真に非同期のサービス応答からデータが更新されたときにも機能するため、私にとって最適なソリューションです(そして、コントローラーで$ evalAsyncを使用する必要はありません)。
gorebash 2013

9
これは廃止されましたか?これを試してみると、実際はDOMがレンダリングされる直前に呼び出されます。シャドードームか何かに依存していますか?
Shayne、2014

私はAngularに比較的慣れていないので、特定のケースでこの回答を実装する方法がわかりません。私はさまざまな答えと推測を見てきましたが、それはうまくいきません。「監視」機能が何を監視すべきかわかりません。私たちの角度アプリには、ページごとに異なるさまざまなeng-ディレクティブがあります。「監視」する対象を知るにはどうすればよいですか?確かに、ページのレンダリングが完了したときにいくつかのコードを起動する簡単な方法はありますか?
moosefetcher 2016

63

まず、レンダリングをいじる適切な場所はディレクティブです。私のアドバイスは、このようなディレクティブで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);
                });
            });
        },
    };
});

これがあなたの闘争を防ぐのに役立つことを願っています。


これは明白なことを述べているかもしれませんが、これがうまくいかない場合は、リンク関数/コントローラーで非同期操作を確認してください。$ evalAsyncがそれらを考慮に入れることはできないと思います。
LOAS 2015年

link関数とを組み合わせることもできますtemplateUrl
Wtower 2016

35

Miskoのアドバイスに従い、非同期操作が必要な場合は、$ timeout()ではなく(機能しません)

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

$ evalAsync()を使用します(機能します)

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

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

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

ただし、すでにご存じのとおり、ここでは非同期操作の必要はないと思います。


4
$scope.$evalAsync($scope.assignmentsLoaded(data));IMOには意味がありません。の引数$evalAsync()は関数でなければなりません。あなたの例では、後で実行するために関数を登録する必要があるときに関数を呼び出しています。
hgoebl 2014

@ hgoebl、$ evalAsyncへの引数は関数または式です。この場合、式を使用しました。しかし、そうです、式はすぐに実行されます。後で実行するために関数にラップするように答えを変更しました。それをキャッチしてくれてありがとう。
Mark Rajcok、2014

26

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

これは物事を行うための最もエレガントな方法ではないことに気づきましたが、それは私にとってはうまくいきます...

同様の状況でしたが、アニメーションの開始時にローディングインジケーターを削除する必要があったところが少し逆になりましたが、モバイルデバイスでは、角度が初期化されるアニメーションが表示されるアニメーションよりもはるかに速く、ng-cloakを使用するとローディングインジケーターが不十分でした。実際のデータが表示されるかなり前に削除されました。この場合、最初にレンダリングされた要素にmy return 0関数を追加し、その関数で、読み込みインジケーターを非表示にする変数を反転しました。(もちろん、この関数によってトリガーされる読み込みインジケーターにng-hideを追加しました。


3
これは確かにハックですが、まさに私が必要としたものです。レンダリング直後にコードを強制的に実行しました(または非常に正確に近い)。理想的な世界では私はこれをしませんが、ここにいます...
Andrew

$anchorScrollDOMがレンダリングされた後にビルトインサービスを呼び出す必要がありましたが、これ以外の方法では何もできませんでした。ハックが動作します。
Pat Migliaccio、2017

ng-initがより良い代替案
Flying Gambit

1
ng-initが常に機能するとは限りません。たとえば、domに多数の画像を追加するng-repeatがあります。各画像がng-repeatに追加された後に関数を呼び出したい場合は、ng-showを使用する必要があります。
Michael Bremerkamp


4

最後に解決策を見つけました。コレクションを更新するためにRESTサービスを使用していました。データテーブルを変換するためのjqueryは次のコードです。

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

3

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



2

$ scope。$ evalAsync()と$ timeout(fn、0)のどちらも、私にとって信頼性の高い動作をしませんでした。

2つを組み合わせる必要がありました。指示を出し、デフォルト値より高い優先度を設定して、適切に測定しました。ここにそのディレクティブがあります(私は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>

1

サービスを更新して新しいビュー(ページ)にリダイレクトし、サービスが更新される前にディレクティブが読み込まれるいくつかのシナリオでは、$ watchまたは$ timeoutが失敗した場合に$ rootScope。$ broadcastを使用できます。

見る

<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);
        }
   };
});

1

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

.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
            });
        }
}]);

コードは実際には、レンダリングの完了後に変更レンダリングした後で実行されることに注意してください。しかし、ほとんどの場合、レンダリングの変更が発生するたびに状況を処理できると思います。また、レンダリングが完了した後でpコードを1 だけ実行する場合は、この長さ(またはその他のメジャー)をモデルと比較することもできます。私はこれについての考え/コメントに感謝します。


0

angular-ui utilsの「jQuery Passthrough」モジュールを使用できます。jQueryタッチカルーセルプラグインをいくつかの画像に正常にバインドし、Webサービスから非同期で取得してng-repeatでレンダリングしました。

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