web-dev-qa-db-ja.com

ng-repeatが終了したときに関数を呼び出す

私が実装しようとしているのは、基本的に "on ng repeat finished rendering"ハンドラです。いつ完了したのかを検出することはできますが、そこから関数をトリガーする方法を理解することはできません。

フィドルをチェックしてください。 http://jsfiddle.net/paulocoelho/BsMqq/3/

JS

var module = angular.module('testApp', [])
    .directive('onFinishRender', function () {
    return {
        restrict: 'A',
        link: function (scope, element, attr) {
            if (scope.$last === true) {
                element.ready(function () {
                    console.log("calling:"+attr.onFinishRender);
                    // CALL TEST HERE!
                });
            }
        }
    }
});

function myC($scope) {
    $scope.ta = [1, 2, 3, 4, 5, 6];
    function test() {
        console.log("test executed");
    }
}

HTML

<div ng-app="testApp" ng-controller="myC">
    <p ng-repeat="t in ta" on-finish-render="test()">{{t}}</p>
</div>

答え:finishmoveからの作業フィドル: http://jsfiddle.net/paulocoelho/BsMqq/4/

247
PCoelho
var module = angular.module('testApp', [])
    .directive('onFinishRender', function ($timeout) {
    return {
        restrict: 'A',
        link: function (scope, element, attr) {
            if (scope.$last === true) {
                $timeout(function () {
                    scope.$emit(attr.onFinishRender);
                });
            }
        }
    }
});

私は.ready()を使用せず、むしろ$timeoutでラップしたことに注意してください。 $timeoutは、ng-繰り返された要素が本当にレンダリングを終了したときに実行されることを確実にします($timeoutは現在のダイジェストサイクルの終わりに実行されるので - そしてsetTimeoutとは異なり内部的に$applyも呼び出します) )そのため、ng-repeatが終了したら、$emitを使用して外部スコープ(兄弟および親スコープ)にイベントを発行します。

そして、あなたのコントローラで、あなたは$onでそれをキャッチすることができます:

$scope.$on('ngRepeatFinished', function(ngRepeatFinishedEvent) {
    //you also get the actual event object
    //do stuff, execute functions -- whatever...
});

このHTMLのようになります。

<div ng-repeat="item in items" on-finish-render="ngRepeatFinished">
    <div>{{item.name}}}<div>
</div>
397

DOMが構築された後、ブラウザがレンダリングされる前にコールバック(つまりtest())を実行したい場合は $ evalAsync を使用します。これはちらつきを防ぐでしょう - ref

if (scope.$last) {
   scope.$evalAsync(attr.onFinishRender);
}

フィドル

レンダリング後にコールバックを本当に呼び出したい場合は、$ timeoutを使用してください。

if (scope.$last) {
   $timeout(function() { 
      scope.$eval(attr.onFinishRender);
   });
}

私はイベントよりも$ evalを好む。イベントでは、イベントの名前を知り、そのイベントのコントローラにコードを追加する必要があります。 $ evalを使用すると、コントローラとディレクティブの間のカップリングが少なくなります。

87
Mark Rajcok

これまでに与えられた回答は、ng-repeatが初めてレンダリングされるときのみ機能します、ただし、動的なng-repeatがある場合は、アイテムを追加/削除/フィルタリングし、ng-repeatがレンダリングされるたびに通知する必要がある場合、これらのソリューションは機能しません。

したがって、ng-repeatが再レンダリングされるたびに通知する必要がある場合初めてではなく、それを行う方法を見つけましたが、それはかなり「ハック」です、しかし、あなたが何をしているのかを知っていれば、うまくいきます。 $filterでこのng-repeatを使用します他の$filterを使用する前に

.filter('ngRepeatFinish', function($timeout){
    return function(data){
        var me = this;
        var flagProperty = '__finishedRendering__';
        if(!data[flagProperty]){
            Object.defineProperty(
                data, 
                flagProperty, 
                {enumerable:false, configurable:true, writable: false, value:{}});
            $timeout(function(){
                    delete data[flagProperty];                        
                    me.$emit('ngRepeatFinished');
                },0,false);                
        }
        return data;
    };
})

これにより、$emitがレンダリングされるたびにng-repeatというイベントがngRepeatFinishedと呼ばれます。

どうやって使うのですか:

<li ng-repeat="item in (items|ngRepeatFinish) | filter:{name:namedFiltered}" >

ngRepeatFinishフィルターは、$scopeで定義されているArrayまたはObjectに直接適用する必要があります。その後、他のフィルターを適用できます。

使用しない方法:

<li ng-repeat="item in (items | filter:{name:namedFiltered}) | ngRepeatFinish" >

最初に他のフィルターを適用してからngRepeatFinishフィルターを適用しないでください。

いつこれを使うべきですか?

ng-repeatによって再レンダリングされたDOM要素の新しいディメンションを考慮する必要があるため、リストのレンダリングが完了した後に特定のcssスタイルをDOMに適用する場合。 (ところで:これらの種類の操作はディレクティブ内で実行する必要があります)

ngRepeatFinishedイベントを処理する関数ですべきでないこと:

  • その関数で$scope.$applyを実行しないでください。実行すると、Angularが検出できない無限ループにAngularを配置します。

  • $scopeプロパティの変更には使用しないでください。これらの変更は、次の$digestループまで、および$scope.$applyを実行できないため、ビューに反映されません。それらは役に立たないでしょう。

「しかし、フィルターはそのように使用することを意図したものではありません!!」

いいえ、彼らはそうではありません、これがハックです、あなたがそれを好まなければそれを使用しないでください。同じことを達成するためのより良い方法を知っているなら、私にそれを知らせてください。

要約する

これはハックです、間違った方法で使用するのは危険です。ng-repeatのレンダリングが完了して問題が発生しないようにスタイルを適用する場合にのみ使用してください。

28
Josep

同じコントローラ上で異なるng-repeatsに対して異なる関数を呼び出す必要がある場合は、次のようにして試すことができます。

指令:

var module = angular.module('testApp', [])
    .directive('onFinishRender', function ($timeout) {
    return {
        restrict: 'A',
        link: function (scope, element, attr) {
            if (scope.$last === true) {
            $timeout(function () {
                scope.$emit(attr.broadcasteventname ? attr.broadcasteventname : 'ngRepeatFinished');
            });
            }
        }
    }
});

あなたのコントローラーで、$ onのイベントをキャッチしてください:

$scope.$on('ngRepeatBroadcast1', function(ngRepeatFinishedEvent) {
// Do something
});

$scope.$on('ngRepeatBroadcast2', function(ngRepeatFinishedEvent) {
// Do something
});

複数のng-repeatを含むテンプレート内

<div ng-repeat="item in collection1" on-finish-render broadcasteventname="ngRepeatBroadcast1">
    <div>{{item.name}}}<div>
</div>

<div ng-repeat="item in collection2" on-finish-render broadcasteventname="ngRepeatBroadcast2">
    <div>{{item.name}}}<div>
</div>
11
MCurbelo

他の解決策は最初のページロードでうまくいきますが、モデルが変更されたときにあなたの関数が呼び出されることを保証する唯一の方法はコントローラから$ timeoutを呼び出すことです。これは$ timeoutを使う実用的な フィドル です。あなたの例ではそれは次のようになります。

.controller('myC', function ($scope, $timeout) {
$scope.$watch("ta", function (newValue, oldValue) {
    $timeout(function () {
       test();
    });
});

ngRepeatは行の内容が新しい場合にのみディレクティブを評価するので、リストから項目を削除してもonFinishRenderは起動しません。たとえば、これらのフィドル emit にフィルタ値を入力してみます。

5
John Harley

2ドルのスコープスコープを使わずに、内容が繰り返しのみのディレクティブを書いているのであれば、かなり簡単な解決策があります(最初のレンダリングだけを考えていると仮定して)。リンク機能では:

const dereg = scope.$watch('$$childTail.$last', last => {
    if (last) {
        dereg();
        // do yr stuff -- you may still need a $timeout here
    }
});

これはレンダリングされたリストのメンバーの幅や高さに基づいてDOMを操作する必要があるディレクティブがある場合に役立ちますが、これは一般的ではありません。提案されている他の解決策として。

4
Semicolon

とても簡単です、これが私のやり方です。

.directive('blockOnRender', function ($blockUI) {
    return {
        restrict: 'A',
        link: function (scope, element, attrs) {
            
            if (scope.$first) {
                $blockUI.blockElement($(element).parent());
            }
            if (scope.$last) {
                $blockUI.unblockElement($(element).parent());
            }
        }
    };
})
1
CPhelefu

フィルタ処理されたngRepeatを使ったこの問題の解決策は、 突然変異 イベントを使ったことですが、これらは推奨されません(すぐに置き換えられない)。

それから私は別の簡単なものを考えました:

app.directive('filtered',function($timeout) {
    return {
        restrict: 'A',link: function (scope,element,attr) {
            var Elm = element[0]
                ,nodePrototype = Node.prototype
                ,timeout
                ,slice = Array.prototype.slice
            ;

            Elm.insertBefore = alt.bind(null,nodePrototype.insertBefore);
            Elm.removeChild = alt.bind(null,nodePrototype.removeChild);

            function alt(fn){
                fn.apply(Elm,slice.call(arguments,1));
                timeout&&$timeout.cancel(timeout);
                timeout = $timeout(altDone);
            }

            function altDone(){
                timeout = null;
                console.log('Filtered! ...fire an event or something');
            }
        }
    };
});

これは親要素のNode.prototypeメソッドにワンタイムの$ timeoutでフックして、連続した変更を監視します。

それは大体正しく働きますが、私はaltDoneが二度呼ばれるといういくつかのケースを得ました。

このディレクティブをngRepeatのに追加します。

1
Sjeiti

私はこの質問に対する答えの中で最も単純な解決策を見ないことにとても驚いています。あなたがしたいことは、繰り返し要素(ngInitディレクティブを持つ要素)にngRepeatディレクティブを追加して$lastngRepeatによってスコープ内に設定された特別な変数)をチェックすることです。これは繰り返し要素がリストの最後であることを示します。 $lastがtrueの場合、最後の要素をレンダリングしているので、必要な関数を呼び出すことができます。

ng-init="$last && test()"

HTMLマークアップの完全なコードは次のようになります。

<div ng-app="testApp" ng-controller="myC">
    <p ng-repeat="t in ta" ng-init="$last && test()">{{t}}</p>
</div>

testはAngular.jsによって提供されるので、呼び出すスコープ関数(この場合はngInit)以外に、アプリケーションに特別なJSコードは必要ありません。テンプレートからアクセスできるように、test関数がスコープ内にあることを確認してください。

$scope.test = function test() {
    console.log("test executed");
}

フィドルを見てください http://jsfiddle.net/yNXS2/ 。あなたが作成したディレクティブは新しいスコープを作成しなかったので、私は邪魔をしませんでした。

$scope.test = function(){...はそれを実現しました。