184

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

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

AfterRenderなど、聞くべきイベントがあるようですが、なかなか見つかりません。指令が進むべき道かもしれませんが、それも早すぎるように見えました。

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

==更新==

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

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

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

4

13 に答える 13

68

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

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

于 2014-06-15T10:15:35.787 に答える
41

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

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

jsfiddleを参照してください。

お役に立てば幸いです

于 2013-04-03T20:48:04.607 に答える
34

Miskoのアドバイスに従って、非同期操作が必要な場合は、$ timeout()の代わりに(これは機能しません)

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

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

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

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

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

ただし、すでにお気づきのように、ここでは非同期操作の必要はないと思います。

于 2012-11-29T05:17:58.063 に答える
28

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

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

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

于 2014-11-16T20:03:40.950 に答える
7

$evalAsynchttp : //docs.angularjs.org/api/ng.$ro​​otScope.Scope#$evalAsyncを探していると思います

于 2012-09-07T05:49:30.777 に答える
4

ついに解決策を見つけました。RESTサービスを使用してコレクションを更新していました。datatable jqueryを変換するには、次のコードを使用します。

$scope.$watchCollection( 'conferences', function( old, nuew ) {
        if( old === nuew ) return;
        $( '#dataTablex' ).dataTable().fnDestroy();
        $timeout(function () {
                $( '#dataTablex' ).dataTable();
        });
    });
于 2014-02-25T18:47:55.663 に答える
3

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

于 2014-06-23T21:49:23.947 に答える
2

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

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

于 2013-07-24T16:24:40.417 に答える
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>
于 2017-03-01T21:22:55.423 に答える
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);
        }
   };
});
于 2014-09-10T11:29:17.750 に答える
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
            });
        }
}]);

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

于 2016-09-27T06:12:12.027 に答える
0

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

于 2013-05-17T10:20:03.427 に答える
0

私のソリューションでは、兄弟ディレクティブが呼び出す関数の定義が含まれているため、最初にロードする必要のあるカスタムディレクティブがいくつかありました。例えば:

<div id="container">
    <custom-directive1></custom-directive1>
    <custom-directive2></custom-directive2>
    <custom-directive3></custom-directive3>
</div>

残念ながら、ここでの解決策はどれも私にはうまくいきませんでした。なぜなら、それらはディレクティブをレンダリングした後にのみ機能し、背後にあるディレクティブコードでは機能しなかったからです。

したがって、上記のソリューションのいずれかを実装したときに、いくつかのロード関数を実行するために、ディレクティブがレンダリングされたとしても、スコープはそれらのディレクティブ内の関数が何であるかを知りませんでした。

そこで、コントローラーのどこにでもオブザーバブルを作成しました。

//Call every time a directive is loaded
$scope.$watch('directiveLoaded', function (value) {
            debugger;
    if (value == document.querySelector('#container').children.length) {
        //Its ok to use childHead as we have only one child scope
        $scope.$$childHead.function1_Of_Directive1();
        $scope.$$childHead.function1_Of_Directive2();
    }
});

次に、これら2つのディレクティブがあります。

scope.$parent.directiveLoaded += 1;

すべてのディレクティブの下部にあります。コントローラではオブザーバブルが定義されているため、変数を更新するたびにdirectiveLoaded、オブザーバブル関数が実行されます。はい、これがハックであることは知っていますが、すべてのディレクティブが最終関数を実行する前にコードビハインドとともにレンダリングを終了することを保証するために支払うのは少額です。

ここでデモを完了するために、後で呼び出す必要のある関数を定義する2つのディレクティブがあります。

指令1

(function () {
    app.directive('customDirective1', function () {
        return {
            restrict: 'E',
            templateUrl: '/directive1.html',
            link: function (scope) {

                scope.function1_Of_Directive1 = function() {
                    scope.function2_Of_Directive2();
                    console.log("F1_D1")
                }
     
                //AT BOTTOM OF EVERY DIRECTIVE
                scope.$parent.directiveLoaded += 1;
            }
        }
    });
})();

指令2

(function () {
    app.directive('customDirective2', function () {
        return {
            restrict: 'E',
            templateUrl: '/directive1.html',
            link: function (scope) {
                
                scope.function1_Of_Directive2 = function() {
                    console.log("F1_D2")
                }
                scope.function2_Of_Directive2 = function() {
                    console.log("F2_D2")
                }

                //AT BOTTOM OF EVERY DIRECTIVE
                scope.$parent.directiveLoaded += 1;
            }
        }
    });
})();
于 2022-01-16T11:40:45.100 に答える