То, что я пытаюсь реализовать, в основном является обработчиком "on ng repeat finished render". Я могу обнаружить, когда это будет сделано, но я не могу понять, как вызвать из него функцию.
Проверьте скрипт: 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>
Ответ: Рабочая скрипка от финиша: http://jsfiddle.net/paulocoelho/BsMqq/4/
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
будет выполняться в конце текущего цикла дайджеста - , и он также вызовет $apply
внутренне, в отличие от setTimeout
). Таким образом, после завершения 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>
$timeout
(который по сути является setTimeout
+ $scope.$apply()
Angular $scope.$apply()
) с setInterval
. $timeout
будет выполняться только один раз в соответствии с условием if
, и это будет в начале следующего цикла $digest
. Для получения дополнительной информации о тайм- аутах JavaScript см .: ejohn.org/blog/how-javascript-timers-work
Используйте $evalAsync, если вы хотите, чтобы ваш обратный вызов (т.е. test()) выполнялся после создания DOM, но перед отображением браузера. Это предотвратит мерцание - ref.
if (scope.$last) {
scope.$evalAsync(attr.onFinishRender);
}
Если вы действительно хотите вызвать обратный вызов после рендеринга, используйте $timeout:
if (scope.$last) {
$timeout(function() {
scope.$eval(attr.onFinishRender);
});
}
Я предпочитаю $eval вместо события. С событием нам нужно знать имя события и добавить код для нашего контроллера для этого события. С $eval между контроллером и директивой существует меньшая связь.
$last
? Не могу найти это в документах. Это было удалено?
$last
определяется в области видимости, если для элемента активен ng-repeat
.
Ответы, которые были даны до сих пор, будут работать только в первый раз, когда 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
событие под названием ngRepeatFinished
каждый раз, когда отображается ng-repeat
.
<li ng-repeat="item in (items|ngRepeatFinish) | filter:{name:namedFiltered}" >
Фильтр ngRepeatFinish
должен применяться непосредственно к Array
или Object
, определенному в вашем $scope
, после этого вы можете применить другие фильтры.
<li ng-repeat="item in (items | filter:{name:namedFiltered}) | ngRepeatFinish" >
Не применяйте другие фильтры сначала, а затем применяйте фильтр ngRepeatFinish
.
Если вы хотите применить определенные стили CSS в DOM после того, как список закончил рендеринг, потому что вам нужно учитывать новые измерения элементов DOM, которые были повторно отображены с помощью ng-repeat
. (BTW: эти операции должны выполняться внутри директивы)
ngRepeatFinished
:Не выполняйте $scope.$apply
в этой функции, иначе вы ставите Angular в бесконечный цикл, который Angular не сможет обнаружить.
Не используйте его для внесения изменений в свойствах $scope
, поскольку эти изменения не будут отображаться в вашем представлении до следующего цикла $digest
, и поскольку вы не можете выполнить $scope.$apply
они не будут использоваться.
Нет, это не так, это хак, если вам это не нравится, не используйте его. Если вы знаете лучший способ выполнить одно и то же, пожалуйста, сообщите мне об этом.
Это хак, и использование его неправильным образом опасно, используйте его только для применения стилей после завершения
ng-repeat
рендеринга, и у вас не должно быть никаких проблем. p >
$last
также изменяется, простая директива ngRepeatFinish
, проверяя $last
сейчас, будет работать. Как только вызывается ngRepeatFinish, я удаляю фиктивный элемент. (Я также скрываю это с помощью CSS, чтобы он не появлялся на короткое время)
Если вам нужно вызвать разные функции для разных ng-повторов на одном контроллере, вы можете попробовать что-то вроде этого:
Директива:
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>
Другие решения будут хорошо работать при начальной загрузке страницы, но вызов $timeout из контроллера - единственный способ гарантировать, что ваша функция будет вызвана при изменении модели. Вот работающий fiddle, который использует $timeout. Для вашего примера это будет:
.controller('myC', function ($scope, $timeout) {
$scope.$watch("ta", function (newValue, oldValue) {
$timeout(function () {
test();
});
});
ngRepeat будет оценивать директиву только тогда, когда содержимое строки будет новым, поэтому, если вы удалите элементы из своего списка, onFinishRender не будет запускаться. Например, попробуйте ввести значения фильтра в этих скриптах emit.
ta
в $scope.$watch("ta",...
?
Если вы не прочь использовать реквизиты с двумя долларами и вы пишете директиву, единственное содержимое которого повторяется, есть довольно простое решение (предполагая, что вы заботитесь только об исходном рендере). В функции связи:
const dereg = scope.$watch('$$childTail.$last', last => {
if (last) {
dereg();
// do yr stuff -- you may still need a $timeout here
}
});
Это полезно для случаев, когда у вас есть директива, которая должна выполнять DOM-манипулирование на основе ширины или высоты элементов визуализированного списка (который, я думаю, является наиболее вероятной причиной того, что вы задаете этот вопрос), но его а не как общие, как другие предлагаемые решения.
Очень просто, вот как я это сделал.
.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());
}
}
};
})
Решение этой проблемы с фильтрованным ngRepeat могло быть с Mutation событиями, но они устарели (без немедленной замены).
Тогда я подумал о другом легком:
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 родительского элемента с тайм-аутом с одним тиком $, чтобы наблюдать за последовательными изменениями.
Он работает в основном правильно, но я получил некоторые случаи, когда altDone будет вызываться дважды.
Снова... добавьте эту директиву в родительский элемент ngRepeat.
appendChild
(так как я использовал только insertBefore
и removeChild
) в приведенном выше коде.
Я очень удивлен тем, что не вижу самого простого решения среди ответов на этот вопрос. То, что вы хотите сделать, это добавить директиву ngInit
для повторяющегося элемента (элемент с директивой ngRepeat
), проверяющий значение $last
(специальная переменная, заданная в области по ngRepeat
которая указывает, что повторяющийся элемент является последним в списке). Если $last
истинно, мы показываем последний элемент, и мы можем вызвать нужную нам функцию.
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>
Вам не нужен дополнительный JS-код в вашем приложении, кроме функции области, которую вы хотите вызвать (в данном случае, test
), поскольку ngInit
предоставляется Angular.js. Просто убедитесь, что у вас есть test
функция в области, чтобы ее можно было получить из шаблона:
$scope.test = function test() {
console.log("test executed");
}
Пожалуйста, посмотрите на скрипку, http://jsfiddle.net/yNXS2/. Поскольку созданная вами директива не создала новую область действия, я продолжал на этом пути.
$scope.test = function(){...
сделал это.
element.ready()
? Я имею в виду ... это какой-то плагин jQuery, который у вас есть, или он должен быть запущен, когда элемент готов?