В любом случае, чтобы вызвать метод, когда Angular сделано с добавлением обновлений области в DOM?
Я ищу способ выполнить код, когда после добавления изменений в переменную $scope, в этом случае $scope.results. Мне нужно сделать это, чтобы вызвать некоторый унаследованный код, который требует, чтобы элементы находились в DOM, прежде чем он сможет выполнить.
Мой реальный код запускает вызов AJAX и обновляет переменную области видимости для обновления ui. Поэтому в настоящее время мой код выполняется сразу после того, как я нажимаю на область действия, но устаревший код не работает, потому что элементы dom еще недоступны.
Я могу добавить уродливую задержку с помощью setTimeout(), но это не гарантирует, что DOM действительно готов.
Мой вопрос: есть ли какие-либо способы привязки к "визуализированному" событию?
var myApp = angular.module('myApp', []);
myApp.controller("myController", ['$scope', function($scope){
var resultsToLoad = [{id: 1, name: "one"},{id: 2, name: "two"},{id: 3, name: "three"}];
$scope.results = [];
$scope.loadResults = function(){
for(var i=0; i < resultsToLoad.length; i++){
$scope.results.push(resultsToLoad[i]);
}
}
function doneAddingToDom(){
// do something awesome like trigger a service call to log
}
}]);
angular.bootstrap(document, ['myApp']);
Ссылка на моделированный код: http://jsfiddle.net/acolchado/BhApF/5/
Спасибо в Advance!
Ответы
Ответ 1
$evalAsync очередь используется для планирования работы, которая должна произойти за пределами текущего фрейма стека, но перед визуализацией браузера. - http://docs.angularjs.org/guide/concepts#runtime
Хорошо, а что такое "стек кадров"? Комментарий Github показывает больше:
если вы ставите в очередь из контроллера, то он будет раньше, но если вы в очереди из директивы, то это будет после. - https://github.com/angular/angular.js/issues/734#issuecomment-3675158
Выше, Misko обсуждает, когда запускается код, который поставлен в очередь для выполнения $evalAsync, в отношении того, когда DOM обновляется Angular. Я также предлагаю прочитать два комментария Github, чтобы получить полный контекст.
Поэтому, если код поставлен в очередь с использованием $evalAsync из директивы, он должен запускаться после того, как DOM был обработан Angular, но до того, как браузер отобразит. Если вам нужно запустить что-то после отображения браузера или после обновления контроллера модели, используйте $timeout(..., 0);
См. также fooobar.com/questions/18515/..., в котором также есть пример скрипта, который использует $evalAsync().
Ответ 2
Я разветкил твою скрипку.
http://jsfiddle.net/xGCmp/7/
Я добавил директиву emit-when. Он принимает два параметра. Событие, которое должно быть выбрано, и условие, которое должно быть выполнено для события, которое должно быть выбрано. Это работает, потому что, когда функция ссылки выполняется в директиве, мы знаем, что элемент был отображен в DOM. Мое решение состоит в том, чтобы испустить событие, когда последний элемент в ng-repeat был отображен.
Если бы у нас было все Angular решение, я бы не рекомендовал это делать. Это своего рода хаки. Но это может быть решение для управления типом устаревшего кода, который вы упоминаете.
var myApp = angular.module('myApp', []);
myApp.controller("myController", ['$scope', function($scope){
var resultsToLoad = [
{id: 1, name: "one"},
{id: 2, name: "two"},
{id: 3, name: "three"}
];
function doneAddingToDom() {
console.log(document.getElementById('renderedList').children.length);
}
$scope.results = [];
$scope.loadResults = function(){
$scope.results = resultsToLoad;
// If run doneAddingToDom here, we will find 0 list elements in the DOM. Check console.
doneAddingToDom();
}
// If we run on doneAddingToDom here, we will find 3 list elements in the DOM.
$scope.$on('allRendered', doneAddingToDom);
}]);
myApp.directive("emitWhen", function(){
return {
restrict: 'A',
link: function(scope, element, attrs) {
var params = scope.$eval(attrs.emitWhen),
event = params.event,
condition = params.condition;
if(condition){
scope.$emit(event);
}
}
}
});
angular.bootstrap(document, ['myApp']);
Ответ 3
Использование тайм-аута - неправильный способ сделать это. Используйте директиву для добавления/управления DOM. Если вы используете таймаут, обязательно используйте $timeout, который подключен к Angular (например, возвращает обещание).
Ответ 4
Если вы похожи на меня, вы заметите, что во многих случаях $timeout с ожиданием 0 работает задолго до того, как DOM действительно стабилен и полностью статичен. Когда я хочу, чтобы DOM был стабильным, я хочу, чтобы он был стабильным. И поэтому решение, с которым я столкнулся, - это установить наблюдателя на элемент (или, как в примере ниже всего документа), для события "DOMSubtreeModified". Как только я подождал 500 миллисекунд, и изменений DOM не было, я транслировал событие типа domRendered.
IE:
//todo: Inject $rootScope and $window,
//Every call to $window.setTimeout will use this function
var broadcast = function () {};
if (document.addEventListener) {
document.addEventListener("DOMSubtreeModified", function (e) {
//If less than 500 milliseconds have passed, the previous broadcast will be cleared.
clearTimeout(broadcast)
broadcast = $window.setTimeout(function () {
//This will only fire after 500 ms have passed with no changes
$rootScope.$broadcast('domRendered')
}, 500)
});
//IE stupidity
} else {
document.attachEvent("DOMSubtreeModified", function (e) {
clearTimeout(broadcast)
broadcast = $window.setTimeout(function () {
$rootScope.$broadcast('domRendered')
}, 500)
});
}
Это событие можно подключить, как и все трансляции, так:
$rootScope.$on("domRendered", function(){
//do something
})
Ответ 5
У меня была настраиваемая директива, и мне понадобилось результирующее свойство height()
element
внутри моей директивы, что означало, что мне нужно было прочитать его после того, как angular выполнил весь $digest
, и браузер вытек из макета.
В функции link
моей директивы;
Это не работает надежно, но не достаточно поздно;
scope.$watch(function() {});
Это было еще недостаточно поздно:
scope.$evalAsync(function() {});
Казалось, что следующее работа (даже с 0ms в Chrome), где любопытно даже ẁindow.setTimeout()
с scope.$apply()
не было;
$timeout(function() {}, 0);
Фликкер был обеспокоен, поэтому, в конце концов, я прибегал к использованию requestAnimationFrame()
с отступлением от $timeout
внутри моей директивы (с соответствующими префиксами поставщика, если это необходимо). Упрощенный, это выглядит примерно так:
scope.$watch("someBoundPropertyIexpectWillAlterLayout", function(n,o) {
$window.requestAnimationFrame(function() {
scope.$apply(function() {
scope.height = element.height(); // OK, this seems to be accurate for the layout
});
});
});
Тогда, конечно, я могу просто использовать a;
scope.$watch("height", function() {
// Adjust view model based on new layout metrics
});
Ответ 6
работает для меня, например:
interval = $interval(function() {
if ($("#target").children().length === 0) {
return;
}
doSomething();
$interval.cancel(interval);
}, 0);