Финишное событие ng-repeat
Я хочу вызвать некоторую функцию jQuery, нацеленную на div с таблицей. Эта таблица заполнена ng-repeat
,
Когда я звоню на
$(document).ready()
У меня нет результата.
Также
$scope.$on('$viewContentLoaded', myFunc);
не помогает
Есть ли способ выполнить функцию сразу после завершения заполнения ng-repeat? Я прочитал совет по использованию пользовательских directive
, но я понятия не имею, как использовать его с ng-repeat и моим div...
15 ответов
Действительно, вы должны использовать директивы, и нет никакого события, связанного с концом цикла ng-Repeat (так как каждый элемент создается индивидуально и имеет свое собственное событие). Но а) использование директив может быть все, что вам нужно, и б) есть несколько специфических свойств ng-Repeat, которые вы можете использовать, чтобы сделать ваше событие "on ngRepeat закончено".
В частности, если все, что вам нужно, это стилизовать / добавить события ко всей таблице, вы можете сделать это, используя директиву, которая охватывает все элементы 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. Надеюсь, поможет!
Если вы просто хотите выполнить некоторый код в конце цикла, вот несколько более простой вариант, который не требует дополнительной обработки событий:
<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');
}
};
});
Нет необходимости создавать директиву, особенно для того, чтобы иметь ng-repeat
завершить мероприятие.
ng-init
делает магию для вас.
<div ng-repeat="thing in things" ng-init="$last && finished()">
$last
удостоверяется, что finished
срабатывает только тогда, когда последний элемент был представлен в DOM.
Не забудьте создать $scope.finished
событие.
Удачного кодирования!
РЕДАКТИРОВАТЬ: 23 октября 2016
Если вы также хотите позвонить 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()">
Вот повторяющаяся директива, которая вызывает указанную функцию, когда истина. Я обнаружил, что вызываемая функция должна использовать $timeout с interval=0 перед выполнением манипуляций с DOM, таких как инициализация всплывающих подсказок на визуализированных элементах. jsFiddle: http://jsfiddle.net/tQw6w/
В $scope.layoutDone попробуйте закомментировать строку $timeout и раскомментировать "НЕ ПРАВИЛЬНО!" линия, чтобы увидеть разницу во всплывающих подсказках.
<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...
}
})
Вот простой подход с использованием ng-init
это даже не требует специальной директивы. Это хорошо работает для меня в определенных сценариях, например, при необходимости автоматической прокрутки div элементов с повторением ng к конкретному элементу при загрузке страницы, поэтому функция прокрутки должна ждать, пока 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. Если вам нужно вызывать функцию всякий раз, когда обновляется содержимое ng-repeat, вам придется использовать один из других ответов в этой теме с пользовательской директивой.
В дополнение к ответу Павла, что-то более читаемое и легко понятное будет:
<ul>
<li ng-repeat="item in items"
ng-init="$last ? doSomething() : angular.noop()">{{item}}</li>
</ul>
Почему еще вы думаете angular.noop
есть ли на первом месте...?
Преимущества:
Вам не нужно писать директиву для этого...
Может быть, немного проще с ngInit
и Лодаш 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 использует фазу предварительной компиляции - то есть выражение вызывается до обработки дочерних директив. Это означает, что все еще может потребоваться асинхронная обработка.
Это также может быть необходимо при проверке scope.$last
переменная, чтобы обернуть ваш триггер с setTimeout(someFn, 0)
, setTimeout 0
является принятой техникой в JavaScript, и это было необходимо для моего directive
бежать правильно.
<div ng-repeat="i in items">
<label>{{i.Name}}</label>
<div ng-if="$last" ng-init="ngRepeatFinished()"></div>
</div>
Моим решением было добавить div для вызова функции, если элемент был последним в повторении.
Я сделал это таким образом.
Создать директиву
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);
После того, как вы добавите его на этикетке, где это нг-повторить
<ul>
<li ng-repeat="(key, value) in data" fin-repeat> {{ value }} </li>
</ul>
И готово с этим будет запускаться в конце ng-repeat.
Это улучшение идей, выраженных в других ответах, чтобы показать, как получить доступ к свойствам ngRepeat ($index, $first, $middle, $last, $even, $odd) при использовании декларативного синтаксиса и изолировать область видимости (Google рекомендовал лучшие практики) с директивой элемента. Обратите внимание на основное отличие: 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!");
}
}
};
});
Я хотел бы добавить еще один ответ, так как предыдущие ответы предполагают, что код, необходимый для запуска после выполнения ngRepeat, является угловым кодом, который в этом случае все ответы выше дают отличное и простое решение, более общее, чем другие, и если важна стадия жизненного цикла дайджеста, вы можете посмотреть в блоге Бена Наделя об этом, за исключением использования $parse вместо $eval.
но по моему опыту, как утверждает OP, обычно он запускает некоторые плагины или методы JQuery на скомпилированном DOM Finnaly, и в этом случае я обнаружил, что самое простое решение - создать директиву с setTimeout, так как функция setTimeout выдвигается до конца очереди браузера, это всегда сразу после того, как все сделано в угловых, обычно ngReapet, который продолжается после того, как его родительская функция postLinking
angular.module('myApp', [])
.directive('pluginNameOrWhatever', function() {
return function(scope, element, attrs) {
setTimeout(function doWork(){
//jquery code and plugins
}, 0);
};
});
для тех, кто интересуется, что в таком случае почему бы не использовать $timeout, он вызывает другой цикл дайджеста, который совершенно не нужен
Мне пришлось рендерить формулы, используя MathJax после окончания ng-repeat, ни один из приведенных выше ответов не решил мою проблему, поэтому я сделал так, как показано ниже. Это не очень хорошее решение, но у меня получилось...
<div ng-repeat="formula in controller.formulas">
<div>{{formula.string}}</div>
{{$last ? controller.render_formulas() : ""}}
</div>
Я нашел ответ здесь хорошо отработанным, но все же необходимо было добавить задержку
Создайте следующую директиву:
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); });
Если вы просто хотите изменить имя класса, чтобы оно отображалось по-разному, приведенный ниже код поможет.
<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.