sự kiện kết thúc lặp lại


207

Tôi muốn gọi một số hàm jQuery nhắm mục tiêu với bảng. Bảng đó được điền với ng-repeat.

Khi tôi gọi nó vào

$(document).ready()

Tôi không có kết quả.

Cũng thế

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

không giúp được gì

Có cách nào để thực thi chức năng ngay sau khi dân số lặp lại hoàn thành không? Tôi đã đọc một lời khuyên về việc sử dụng tùy chỉnh directive, nhưng tôi không biết làm thế nào để sử dụng nó với ng-repeat và div của tôi ...

Câu trả lời:


240

Thật vậy, bạn nên sử dụng các chỉ thị và không có sự kiện nào được gắn vào cuối vòng lặp lặp lại (vì mỗi phần tử được xây dựng riêng lẻ và có sự kiện riêng). Nhưng a) sử dụng các lệnh có thể là tất cả những gì bạn cần và b) có một vài thuộc tính cụ thể lặp lại mà bạn có thể sử dụng để tạo sự kiện "trên ngRepeat kết thúc".

Cụ thể, nếu tất cả những gì bạn muốn là tạo kiểu / thêm sự kiện cho toàn bộ bảng, bạn có thể thực hiện bằng cách sử dụng một lệnh bao gồm tất cả các phần tử ngRepeat. Mặt khác, nếu bạn muốn giải quyết cụ thể từng yếu tố, bạn có thể sử dụng một lệnh trong ngRepeat và nó sẽ hành động trên từng thành phần, sau khi nó được tạo.

Sau đó, có những $index, $first, $middle$lastcác thuộc tính bạn có thể sử dụng các sự kiện kích hoạt. Vì vậy, đối với HTML này:

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

Bạn có thể sử dụng các chỉ thị như vậy:

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

Xem nó trong hành động trong Plunker này . Hy vọng nó giúp!


Tôi có thể myMainDirectivethực hiện mã của mình chỉ sau khi kết thúc vòng lặp không? Tôi cần cập nhật thanh cuộn của div cha.
ChruS

2
Tất nhiên! Chỉ cần thay đổi "window.alert" thành chức năng bắn sự kiện và bắt nó bằng chỉ thị chính. Tôi đã cập nhật de Plunker để làm điều này, làm ví dụ.
Tiago Roldão

9
Bạn nên đưa thư viện jQuery vào trước (trước Angular). Điều này sẽ khiến tất cả các phần tử Angular được gói bằng jQuery (thay vì jqLite). Sau đó, thay vì angular.element (phần tử) .css () và $ (phần tử) .children (). Css () bạn có thể chỉ cần viết phần tử.css () và Element.children (). Css (). Xem thêm nhóm.google.com/d/msg/angular/6A3Skwm59Z4/oJ0WhKGAFK0J
Mark Rajcok

11
Any1 có bất kỳ ý tưởng nào để làm cho điều này hoạt động cho các kết xuất tiếp theo trên chỉ thị ngRepeat không? tức là: liên kết
RavenHursT

1
Đây là một quy ước đặt tên cho tất cả các chỉ thị góc. Xem docs.angularjs.org/guide/directive#n normalization
Tiago Roldão

68

Nếu bạn chỉ đơn giản muốn thực thi một số mã ở cuối vòng lặp, thì đây là một biến thể đơn giản hơn một chút mà không yêu cầu xử lý sự kiện bổ sung:

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

Xem bản demo trực tiếp.


Nó có hoạt động không nếu tôi sử dụng bộ điều khiển như cú pháp? Xin vui lòng? Trong không, có cách nào khác làm việc với bộ điều khiển như không?
Dân Nguyễn

63

Không cần tạo ra một chỉ thị đặc biệt là chỉ để có một ng-repeatsự kiện hoàn chỉnh.

ng-init làm điều kỳ diệu cho bạn

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

các $lastđảm bảo, rằngfinished chỉ bị sa thải, khi yếu tố cuối cùng đã được trả lại vào DOM.

Đừng quên tạo $scope.finished sự kiện.

Chúc mừng mã hóa !!

EDIT: 23 tháng 10 năm 2016

Trong trường hợp bạn cũng muốn gọi finishedhàm khi không có mục nào trong mảng thì bạn có thể sử dụng cách giải quyết sau

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

Chỉ cần thêm dòng trên vào đầu ng-repeat phần tử. Nó sẽ kiểm tra nếu mảng không có bất kỳ giá trị nào và gọi hàm tương ứng.

Ví dụ

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

Điều gì xảy ra nếu "những thứ" hóa ra là một mảng trống?
Frane Poljak

1
@FranePoljak Tôi có thêm giải pháp trong câu trả lời.
Vikas Bansal

1
Trong trường hợp mảng trống, hãy xử lý nó trong bộ điều khiển
JSAddict

Khắc phục câu trả lời Vikas Bansal: // sử dụng div đầu tiên nếu bạn muốn kiểm tra xem mảng có không có giá trị nào không và gọi hàm cho phù hợp. <div ng-if = "! Things.length" ng-hide = "true" ng-init = "finish ()"> </ div> <div ng-repeat = "thing in thing" ng-init = "$ cuối cùng && đã hoàn thành () ">
Alex Teslenko

41

Dưới đây là một lệnh được thực hiện lặp lại gọi một hàm được chỉ định khi đúng. Tôi đã thấy rằng hàm được gọi phải sử dụng $ timeout với distance = 0 trước khi thực hiện thao tác DOM, chẳng hạn như khởi tạo các chú giải công cụ trên các phần tử được kết xuất. jsFiddle: http://jsfiddle.net/tQw6w/

Trong $ scope.layoutDone, hãy thử nhận xét dòng $ timeout và bỏ ghi chú "KHÔNG ĐÚNG!" dòng để thấy sự khác biệt trong các chú giải công cụ.

<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...
        }

    })

Tôi không muốn sử dụng thời gian chờ $, nhưng khi bạn nói nó có thể được đặt thành 0, tôi đã quyết định dùng thử và nó hoạt động tốt. Tôi tự hỏi liệu đây có phải là vấn đề về tiêu hóa không, và liệu có cách nào để làm những gì mà thời gian chờ $ đang làm mà không sử dụng $ timeout.
Jameela Huq

Bạn có thể vui lòng giải thích tại sao điều này làm việc? Tôi đang cố gắng truy cập ID động và nó chỉ hoạt động sau khi hết thời gian chờ với độ trễ 0. Tôi đã thấy giải pháp "hacky" này trong một số bài viết nhưng không ai giải thích được tại sao nó hoạt động.
Jin

30

Đây là một cách tiếp cận đơn giản bằng cách sử dụng ng-initmà thậm chí không yêu cầu một chỉ thị tùy chỉnh. Nó hoạt động tốt với tôi trong một số trường hợp nhất định, ví dụ như cần tự động cuộn một div các mục lặp lại sang một mục cụ thể khi tải trang, vì vậy chức năng cuộn cần phải đợi cho đến khi ng-repeatkết xuất hoàn tất vào DOM trước khi có thể kích hoạt.

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

    };
});

Lưu ý rằng điều này chỉ hữu ích cho các chức năng bạn cần gọi một lần sau khi ng-repeat kết xuất lại ban đầu. Nếu bạn cần gọi một hàm bất cứ khi nào nội dung lặp lại được cập nhật thì bạn sẽ phải sử dụng một trong những câu trả lời khác trên chuỗi này với một lệnh tùy chỉnh.


1
Đẹp, điều này đã làm công việc. Tôi muốn tự động chọn văn bản trong hộp văn bản và thời gian chờ đã thực hiện thủ thuật. Mặt khác, văn bản {{model.value}} đã được chọn và sau đó được bỏ chọn khi model.value bị ràng buộc dữ liệu được đưa vào.
JoshGough

27

Bổ sung cho câu trả lời của Pavel , một cái gì đó dễ đọc và dễ hiểu hơn sẽ là:

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

Tại sao bạn nghĩ angular.noop có ở nơi đầu tiên ...?

Ưu điểm:

Bạn không cần phải viết một chỉ thị cho việc này ...


20
Tại sao nên sử dụng một ternary khi bạn có thể sử dụng logic boolean:ng-init="$last && doSomething( )"
Nate Whittaker

Nó dễ đọc hơn @NateWhittaker (và khó đọc hơn một toán tử ternary là ấn tượng). Thật tốt khi có mã sạch và đơn giản để hiểu mã
Justin

21

Có lẽ cách tiếp cận đơn giản hơn một chút với ngInitvà của Lodashdebounce phương pháp mà không cần chỉ thị tùy chỉnh:

Điều khiển:

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

Bản mẫu:

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

Cập nhật

Thậm chí còn có giải pháp AngularJS thuần túy đơn giản hơn bằng cách sử dụng toán tử ternary:

Bản mẫu:

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

Xin lưu ý rằng ngInit sử dụng giai đoạn biên dịch tiền liên kết - tức là biểu thức được gọi trước khi các lệnh con được xử lý. Điều này có nghĩa là vẫn có thể yêu cầu xử lý không đồng bộ.


Cần lưu ý rằng bạn cần lodash.js để nó hoạt động :) Ngoài ra: phần ", 20" của $ scope.refresh =, có phải số millis trước khi phương thức này được gọi sau lần lặp cuối cùng không?
Jørgen Skår Fischer

Thêm Lodash ở phía trước của debounce liên kết. Có, 20 là độ trễ trước khi phương thức được thực thi - thay đổi thành 0 vì nó không quan trọng miễn là cuộc gọi không đồng bộ.
Pavel Horal

1
Cũng nghĩ về điều này, vì toán tử ternary được phép trong các biểu thức đơn giản ng-init="$last ? refresh() : false"cũng sẽ hoạt động. Và điều đó thậm chí không cần đến Lodash.
Pavel Horal

<div ng-repeat = "i in [1,2,4]" ng-init = "doS Something ($ last)"> </ div> $ scope.doSthing = function (lastEuity) {if (lastElem) blah blah blah }
JSAddict

4

Nó cũng có thể là cần thiết khi bạn kiểm tra scope.$lastbiến để bọc kích hoạt của bạn với a setTimeout(someFn, 0). A setTimeout 0là một kỹ thuật được chấp nhận trong javascript và bắt buộc tôi directivephải chạy chính xác.


tôi tìm thấy vấn đề tương tự cả trong xương sống + góc cạnh, nếu bạn cần làm gì đó như đọc các giá trị thuộc tính css được áp dụng cho một phần tử DOM, tôi không thể tìm ra cách hack thời gian chờ.
chovy

3

Đây là một cải tiến của các ý tưởng được thể hiện trong các câu trả lời khác để chỉ ra cách đạt được quyền truy cập vào các thuộc tính ngRepeat ($ index, $ first, $ middle, $ last, $ thậm chí, $ lẻ) khi sử dụng cú pháp khai báo và cách ly phạm vi ( Google khuyến nghị thực hành tốt nhất) với chỉ thị phần tử. Lưu ý sự khác biệt chính : 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!");
      }
    }
  };
});

Sẽ không phải là ý tưởng tốt hơn để sử dụng các chỉ thị như attribs hơn các yếu tố? tức là hạn chế: 'A'?
Tejasvi Hegde

2
Vấn đề là hiển thị cú pháp khai báo + phạm vi cô lập ... có, bạn được hoan nghênh sử dụng một chỉ thị thuộc tính ở đây nếu bạn muốn. Có một thời gian và địa điểm cho một trong hai tùy thuộc vào mục đích của bạn.
JoshuaDavid

3

Tôi đã làm theo cách này.

Tạo chỉ thị

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

Sau khi bạn thêm nó vào nhãn nơi ng-lặp lại này

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

Và sẵn sàng với điều đó sẽ được chạy vào cuối ng-repeat.


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

Giải pháp của tôi là thêm một div để gọi hàm nếu mục đó là lần cuối cùng lặp lại.


2

Tôi muốn thêm một câu trả lời khác, vì các câu trả lời trước cho rằng mã cần chạy sau khi ngRepeat được thực hiện là một mã góc, trong trường hợp đó, tất cả các câu trả lời ở trên đều đưa ra một giải pháp tuyệt vời và đơn giản, một số chung chung hơn các câu hỏi khác, và trong trường hợp quan trọng là giai đoạn vòng đời tiêu hóa, bạn có thể xem blog của Ben Nadel về nó, ngoại trừ việc sử dụng $ parse thay vì $ eval.

nhưng theo kinh nghiệm của tôi, như OP tuyên bố, nó thường chạy một số bổ trợ hoặc phương thức JQuery trên DOM được biên dịch hoàn chỉnh, trong trường hợp đó tôi thấy rằng giải pháp đơn giản nhất là tạo một lệnh với setTimeout, vì hàm setTimeout bị đẩy đến cuối hàng đợi của trình duyệt, nó luôn luôn đúng sau khi mọi thứ được thực hiện trong góc, thường là ngReapet tiếp tục sau chức năng postLinking của cha mẹ

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

cho bất cứ ai tự hỏi rằng trong trường hợp đó tại sao không sử dụng $ timeout, thì nó gây ra một chu trình tiêu hóa khác hoàn toàn không cần thiết


1

Tôi đã phải kết xuất công thức bằng MathJax sau khi ng-repeat kết thúc, không có câu trả lời nào ở trên giải quyết được vấn đề của tôi, vì vậy tôi đã thực hiện như bên dưới. Đó không phải là một giải pháp tốt , nhưng làm việc cho tôi ...

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

0

Tôi đã tìm thấy một câu trả lời ở đây được thực hành tốt, nhưng vẫn cần thêm một sự chậm trễ

Tạo chỉ thị sau:

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

Thêm nó vào bộ lặp của bạn như một thuộc tính, như thế này:

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

Theo Radu ,:

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

Đối với tôi nó hoạt động, nhưng tôi vẫn cần thêm setTimeout

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

-4

Nếu bạn chỉ đơn giản muốn thay đổi tên lớp để nó được hiển thị khác đi, mã bên dưới sẽ thực hiện thủ thuật.

<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>

Mã này hoạt động với tôi khi tôi có một yêu cầu tương tự để hiển thị mặt hàng đã mua trong danh sách mua sắm của mình bằng phông chữ Strick.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.