ng-repeat終了イベント


207

テーブルでdivをターゲットとするjQuery関数を呼び出したいのですが。そのテーブルにはが入力されng-repeatます。

私がそれを呼び出すとき

$(document).ready()

結果はありません。

また

$scope.$on('$viewContentLoaded', myFunc);

助けにはならない。

ng-repeatポピュレーションが完了した直後に関数を実行する方法はありますか?customの使用に関するアドバイスを読みましたが、directiveng-repeatと私のdivでそれを使用する方法がわかりません...

回答:


240

実際、ディレクティブを使用する必要があり、ng-Repeatループの終わりに関連付けられたイベントはありません(各要素は個別に作成され、独自のイベントを持っているため)。ただし、a)ディレクティブを使用するだけで十分な場合もあり、b)「on ngRepeat finished」イベントを作成するために使用できるng-Repeat固有のプロパティがいくつかあります。

具体的には、イベントをテーブル全体にスタイル/追加することだけが必要な場合は、すべてのngRepeat要素を含むディレクティブで使用できます。一方、各要素を具体的にアドレス指定する場合は、ngRepeat内でディレクティブを使用できます。ディレクティブは、作成後に各要素に作用します。

すると、そこにある$index$first$middleおよび$lastプロパティは、トリガ・イベントに使用することができます。したがって、このHTMLの場合:

<div ng-controller="Ctrl" my-main-directive>
  <div ng-repeat="thing in things" my-repeat-directive>
    thing {{thing}}
  </div>
</div>

次のようなディレクティブを使用できます。

angular.module('myApp', [])
.directive('myRepeatDirective', function() {
  return function(scope, element, attrs) {
    angular.element(element).css('color','blue');
    if (scope.$last){
      window.alert("im the last!");
    }
  };
})
.directive('myMainDirective', function() {
  return function(scope, element, attrs) {
    angular.element(element).css('border','5px solid red');
  };
});

このPlunkerの実際の動作をご覧ください。それが役に立てば幸い!


することができます私のメイクmyMainDirectiveのコードでは、ループの終了後にのみ実行しますか?親divのスクロールバーを更新する必要があります。
ChruS

2
もちろん!「window.alert」をイベント発生関数に変更し、メインディレクティブでキャッチします。例として、これを行うためにde Plunkerを更新しました。
TiagoRoldão12年

9
最初に(Angularの前に)jQueryライブラリを含めることをお勧めします。これにより、すべてのAngular要素が(jqLit​​eではなく)jQueryでラップされます。次に、angular.element(element).css()および$(element).children()。css()の代わりに、単にelement.css()およびelement.children()。css()と記述できます。groups.google.com/d/msg/angular/6A3Skwm59Z4/oJ0WhKGAFK0J
Mark Rajcok

11
Any1は、これをngRepeatディレクティブの後続のレンダリングで機能させる方法を知っていますか?ie:リンク
RavenHursT 2014年

1
これは、すべての角度ディレクティブの命名規則です。docs.angularjs.org/guide/directive#normalization
TiagoRoldãoApr

68

ループの最後でコードを実行するだけの場合は、追加のイベント処理を必要としない、もう少し簡単なバリエーションを次に示します。

<div ng-controller="Ctrl">
  <div class="thing" ng-repeat="thing in things" my-post-repeat-directive>
    thing {{thing}}
  </div>
</div>
function Ctrl($scope) {
  $scope.things = [
    'A', 'B', 'C'  
  ];
}

angular.module('myApp', [])
.directive('myPostRepeatDirective', function() {
  return function(scope, element, attrs) {
    if (scope.$last){
      // iteration is complete, do whatever post-processing
      // is necessary
      element.parent().css('border', '1px solid black');
    }
  };
});

ライブデモをご覧ください。


構文としてコントローラーを使用する場合、これは機能しますか?お願いします?ではなく、コントローラで動作する他の方法はありますか?
Dan Nguyen

63

特にng-repeat完全なイベントを持つためだけにディレクティブを作成する必要はありません。

ng-init あなたのための魔法をします。

  <div ng-repeat="thing in things" ng-init="$last && finished()">

これ$lastによりfinished、最後の要素がDOMにレンダリングされたときにのみ発生します。

$scope.finishedイベントを作成することを忘れないでください。

ハッピーコーディング!!

編集:2016年10月23日

finished配列にアイテムがないときにも関数を呼び出す場合は、次の回避策を使用できます。

<div style="display:none" ng-init="things.length < 1 && finished()"></div>
//or
<div ng-if="things.length > 0" ng-init="finished()"></div>

上記の行をng-repeat要素の上部に追加するだけです。配列に値がないかどうかを確認し、それに応じて関数を呼び出します。

例えば

<div ng-if="things.length > 0" ng-init="finished()"></div>
<div ng-repeat="thing in things" ng-init="$last && finished()">

「もの」が空の配列であると判明した場合はどうなりますか?
Frane Poljak

1
@FranePoljak答えにソリューションを追加しました。
Vikas Bansal

1
空の配列の場合は、コントローラーで処理します
JSAddict

Vikas Bansalの回答を修正://配列に値がないかどうかを確認し、それに応じて関数を呼び出す場合は、最初のdivを使用します。<div ng-if = "!things.length" ng-hide = "true" ng-init = "finished()"> </ div> <div ng-repeat = "thing in things" ng-init = "$ last && finished() ">
Alex Teslenko 2017

41

以下は、trueのときに指定された関数を呼び出すrepeat-doneディレクティブです。呼び出された関数は、レンダリングされた要素のツールチップの初期化などのDOM操作を行う前に、interval = 0の$ timeoutを使用する必要があることを発見しました。jsFiddle:http ://jsfiddle.net/tQw6w/

$ scope.layoutDoneで、$ timeout行をコメント化し、「NOT CORRECT!」のコメントを外してみてください。行でツールチップの違いを確認します。

<ul>
    <li ng-repeat="feed in feedList" repeat-done="layoutDone()" ng-cloak>
    <a href="{{feed}}" title="view at {{feed | hostName}}" data-toggle="tooltip">{{feed | strip_http}}</a>
    </li>
</ul>

JS:

angular.module('Repeat_Demo', [])

    .directive('repeatDone', function() {
        return function(scope, element, attrs) {
            if (scope.$last) { // all are rendered
                scope.$eval(attrs.repeatDone);
            }
        }
    })

    .filter('strip_http', function() {
        return function(str) {
            var http = "http://";
            return (str.indexOf(http) == 0) ? str.substr(http.length) : str;
        }
    })

    .filter('hostName', function() {
        return function(str) {
            var urlParser = document.createElement('a');
            urlParser.href = str;
            return urlParser.hostname;
        }
    })

    .controller('AppCtrl', function($scope, $timeout) {

        $scope.feedList = [
            'http://feeds.feedburner.com/TEDTalks_video',
            'http://feeds.nationalgeographic.com/ng/photography/photo-of-the-day/',
            'http://sfbay.craigslist.org/eng/index.rss',
            'http://www.slate.com/blogs/trending.fulltext.all.10.rss',
            'http://feeds.current.com/homepage/en_US.rss',
            'http://feeds.current.com/items/popular.rss',
            'http://www.nytimes.com/services/xml/rss/nyt/HomePage.xml'
        ];

        $scope.layoutDone = function() {
            //$('a[data-toggle="tooltip"]').tooltip(); // NOT CORRECT!
            $timeout(function() { $('a[data-toggle="tooltip"]').tooltip(); }, 0); // wait...
        }

    })

私は$ timeoutを使いたくありませんでしたが、0に設定できると言ったとき、試してみることにしましたが、うまくいきました。これは消化の問題かどうか、そして$ timeoutを使用せずに$ timeoutが実行していることを実行する方法があるかどうか疑問に思います。
Jameela Huq 2016

なぜこれが機能するのか説明していただけますか?動的IDにアクセスしようとしていますが、遅延が0のタイムアウトが発生した後にのみ機能します。私はいくつかの投稿でこの「ハッキー」なソリューションを見ましたが、それが機能する理由を説明する人はいません。
ジン

30

ng-initカスタムディレクティブを必要としない簡単なアプローチを次に示します。これは、ページの読み込み時にng-repeatedアイテムのdivを特定のアイテムに自動スクロールする必要がある場合など、特定のシナリオでうまく機能します。そのため、スクロール機能はng-repeat、がDOMへのレンダリングを完了するまで待機してから、それを実行できます。

<div ng-controller="MyCtrl">
    <div ng-repeat="thing in things">
        thing: {{ thing }}
    </div>
    <div ng-init="fireEvent()"></div>
</div>

myModule.controller('MyCtrl', function($scope, $timeout){
    $scope.things = ['A', 'B', 'C'];

    $scope.fireEvent = function(){

        // This will only run after the ng-repeat has rendered its things to the DOM
        $timeout(function(){
            $scope.$broadcast('thingsRendered');
        }, 0);

    };
});

これは、ng-repeatが最初にレンダリングされた後に1回呼び出す必要がある関数に対してのみ役立つことに注意してください。ng-repeatの内容が更新されるたびに関数を呼び出す必要がある場合は、このスレッドでカスタムディレクティブを使用して他の回答のいずれかを使用する必要があります。


1
美しい、これは仕事をしました。テキストボックス内のテキストを自動選択したかったのですが、タイムアウトで解決しました。それ以外の場合、{{model.value}}テキストは選択され、データバインドされたmodel.valueが挿入されたときに選択解除されました。
JoshGough、2015年

27

Pavelの回答を補足すると、より読みやすく、簡単に理解できるものは次のようになります。

<ul>
    <li ng-repeat="item in items" 
        ng-init="$last ? doSomething() : angular.noop()">{{item}}</li>
</ul>

angular.noopそもそもなぜ他にあると思う?

利点:

このためのディレクティブを作成する必要はありません...


20
ブール論理を使用できるときに三項を使用する理由:ng-init="$last && doSomething( )"
Nate Whittaker '10

@NateWhittakerは読みやすくなっています(3項演算子より読みにくいのは印象的です)。クリーンでわかりやすいコード
Justin

21

おそらく、カスタムディレクティブを必要としないngInitLodashのdebounceメソッドを使用した、もう少し簡単な方法です。

コントローラ:

$scope.items = [1, 2, 3, 4];

$scope.refresh = _.debounce(function() {
    // Debounce has timeout and prevents multiple calls, so this will be called 
    // once the iteration finishes
    console.log('we are done');
}, 0);

テンプレート:

<ul>
    <li ng-repeat="item in items" ng-init="refresh()">{{item}}</li>
</ul>

更新

三項演算子を使用したさらに単純な純粋なAngularJSソリューションがあります。

テンプレート:

<ul>
    <li ng-repeat="item in items" ng-init="$last ? doSomething() : null">{{item}}</li>
</ul>

ngInitはリンク前のコンパイルフェーズを使用することに注意してください。つまり、子ディレクティブが処理される前に式が呼び出されます。つまり、依然として非同期処理が必要になる場合があります。


これを機能させるにはlodash.jsが必要であることに注意してください:)また、$ scope.refresh =の "、20"の部分は、このメソッドが最後の終了後に呼び出されるまでのミリ秒数ですか?
ヨルゲンSKÄRフィッシャー

デバウンスリンクの前にLodashを追加しました。はい、20はメソッドが実行される前の遅延です。呼び出しが非同期である限り問題ではないため、0に変更されます。
Pavel Horal 2014年

1
また、これについて考えると、3項演算子は式で許可されているため、単純ng-init="$last ? refresh() : false"でも機能します。そして、それはLodashを必要としません。
Pavel Horal 2014年

<div ng-repeat = "i in [1,2,4]" ng-init = "doSomething($ last)"> </ div> $ scope.doSomething = function(lastElement){if(lastElem)何とか何とか何とか}
JSAddict 2016年

4

scope.$last変数をチェックして、トリガーをでラップする必要がある場合もありますsetTimeout(someFn, 0)。A setTimeout 0はJavaScriptで受け入れられている手法であり、私directiveが正しく実行することが不可欠でした。


同じ問題が見つかりました。バックボーンと角度の両方で、DOM要素に適用されたcssプロパティ値の読み取りなどを行う必要がある場合、タイムアウトハックなしの方法を理解できませんでした。
2015年

3

これは、宣言構文を使用してスコープを分離するときにngRepeatプロパティ($ index、$ first、$ middle、$ last、$ even、$ odd)にアクセスする方法を示すために、他の回答で表現されたアイデアの改善です。 Googleはベストプラクティスを推奨しています)をelement-directiveとともに使用します。主な違いに注意してくださいscope.$parent.$last

angular.module('myApp', [])
.directive('myRepeatDirective', function() {
  return {
    restrict: 'E',
    scope: {
      someAttr: '='
    },
    link: function(scope, element, attrs) {
      angular.element(element).css('color','blue');
      if (scope.$parent.$last){
        window.alert("im the last!");
      }
    }
  };
});

要素としてよりも属性としてディレクティブを使用する方が良いでしょうか?すなわち、制限: 'A'?
Tejasvi Hegde

2
ポイントは、宣言構文+スコープの分離を示すことでした...はい、必要に応じてここで属性デ​​ィレクティブを使用できます。目的に応じてどちらにも時間と場所があります。
JoshuaDavid 2015

3

私はこのようにしました。

ディレクティブを作成する

function finRepeat() {
    return function(scope, element, attrs) {
        if (scope.$last){
            // Here is where already executes the jquery
            $(document).ready(function(){
                $('.materialboxed').materialbox();
                $('.tooltipped').tooltip({delay: 50});
            });
        }
    }
}

angular
    .module("app")
    .directive("finRepeat", finRepeat);

このng-repeatをラベルに追加した後

<ul>
    <li ng-repeat="(key, value) in data" fin-repeat> {{ value }} </li>
</ul>

そして、ng-repeatの最後に準備ができて実行されます。


3
<div ng-repeat="i in items">
        <label>{{i.Name}}</label>            
        <div ng-if="$last" ng-init="ngRepeatFinished()"></div>            
</div>

私の解決策は、アイテムが繰り返しの最後である場合に関数を呼び出すdivを追加することでした。


2

上記の回答は、ngRepeatの実行後に実行する必要があるコードが角度コードであることを前提としているため、別の回答を追加したいと思います。この場合、上記のすべての回答は、他よりも一般的で優れたシンプルなソリューションを提供します。そして、重要なダイジェストライフサイクルステージの場合は、$ evalの代わりに$ parseを使用することを除いて、Ben Nadelのブログをご覧ください。

しかし、私の経験では、OPが述べているように、通常は最終的にコンパイルされたDOMでいくつかのJQueryプラグインまたはメソッドを実行します。この場合、setTimeout関数がプッシュされるため、最も簡単な解決策はsetTimeoutでディレクティブを作成することです。ブラウザのキューの最後まで、常にすべてが角度で実行された直後、通常は親のpostLinking関数の後に続くngReapet

angular.module('myApp', [])
.directive('pluginNameOrWhatever', function() {
  return function(scope, element, attrs) {        
    setTimeout(function doWork(){
      //jquery code and plugins
    }, 0);        
  };
});

その場合$ timeoutを使用しないのはなぜだろうと思っている人にとっては、それが完全に不要な別のダイジェストサイクルを引き起こすということです。


1

ng-repeatの終了後、MathJaxを使用して数式をレンダリングする必要がありました。上記の回答で問題が解決されなかったため、以下のようにしました。それは良い解決策ではありませんが、私にとってはうまくいきました...

<div ng-repeat="formula in controller.formulas">
    <div>{{formula.string}}</div>
    {{$last ? controller.render_formulas() : ""}}
</div>

0

私はここでよく練習された答えを見つけましたが、それでも遅延を追加する必要がありました

次のディレクティブを作成します。

angular.module('MyApp').directive('emitLastRepeaterElement', function() {
return function(scope) {
    if (scope.$last){
        scope.$emit('LastRepeaterElement');
    }
}; });

次のように、属性としてリピーターに追加します。

<div ng-repeat="item in items" emit-last-repeater-element></div>

ラドゥによると、

$scope.eventoSelecionado.internamento_evolucoes.forEach(ie => {mycode});

私にとってはうまくいきますが、それでもsetTimeoutを追加する必要があります

$scope.eventoSelecionado.internamento_evolucoes.forEach(ie => {
setTimeout(function() { 
    mycode
}, 100); });

-4

別にレンダリングされるようにクラス名を変更したいだけの場合は、以下のコードでうまくいきます。

<div>
<div ng-show="loginsuccess" ng-repeat="i in itemList">
    <div id="{{i.status}}" class="{{i.status}}">
        <div class="listitems">{{i.item}}</div>
        <div class="listitems">{{i.qty}}</div>
        <div class="listitems">{{i.date}}</div>
        <div class="listbutton">
            <button ng-click="UpdateStatus(i.$id)" class="btn"><span>Done</span></button>
            <button ng-click="changeClass()" class="btn"><span>Remove</span></button>
        </div>
    <hr>
</div>

このコードは、買い物リスト内の買い物した商品をStrick troughフォントでレンダリングするという同様の要件がある場合に役立ちました。

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