Если вы уже создавали свои собственные директивы, можно не сомневаться, что видели одно из двух сообщений:
$apply is already in progress.$digest is already in progress.
Эти сообщения об ошибках указывают, что вы пытаетесь сказать Ангуляру о коде, который он уже знает. В лучшем случае, просто увидите эту ошибку в консоли. В худшем — возникнет рекурсивное фиаско, которое сломает браузер (один из немногих недостатков Firebug).
Скорее всего вы получили эту ошибку, потому что находились внутри директивы, и пытались сказать Ангуляру обо всех изменениях в $scope, которые произошли внутри директивы. И, хотя философия ваших действий верна, существуют части директивы, которые Ангуляр уже мониторил. В частности, Ангуляр уже знает о:
— связующей функции
— обработчиках $observe() для атрибутов
— обработчиках $watch() для $scope
Если вы вносите изменения в $scope внутри кода, выполняемого синхронно в связующей функции или асинхронно внутри $observe() и $watch() обработчиков, то действия уже выполняются в контексте Ангуляра. Это означает, что Ангуляр выполнит грязную проверку после выполнения вашего кода и вызовет дополнительные $digest циклы, если необходимо.
Чтобы продемонстрировать это, я написал небольшую директиву, которая отслеживает событие загрузки изображения. Если во время выполнения директивы изображение уже загружено, обработчик вызывается сразу — в контексте Ангуляра. Если изображение еще не загружено, обработчик вызывается асинхронно и необходимо явно уведомить Ангуляр об изменениях.
Примечание: Обработчики $observe() и $watch() показаны в демонстрационных целях. Они ничего не делают.
<!doctype html>
<html ng-app="Demo">
<head>
<meta charset="utf-8" />
</head>
<body>
<ul ng-controller="ListController">
<!-- У этих изображений динамические src-атрибуты -->
<li ng-repeat="image in images">
<p>Loaded: {{ image.complete }}</p>
<img ng-src="{{ image.source }}" bn-load="imageLoaded( image )" />
</li>
<!-- У этого изображения статический src-атрибут -->
<li>
<p>Loaded: {{ staticImage.complete }}</p>
<img src="4.png" bn-load="imageLoaded( staticImage )" />
</li>
</ul>
<!-- Загрузка jQuery и AngularJS -->
<script type="text/javascript" src="//code.jquery.com/jquery-1.9.0.min.js"></script>
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.4/angular.min.js"></script>
<script type="text/javascript">
// Создаем модуль приложения
var Demo = angular.module( "Demo", [] );
// Контроллер для списка изображений
Demo.controller("ListController", function( $scope ) {
// Помечаем изображения как загруженные
$scope.imageLoaded = function( image ) {
image.complete = true;
};
// Настройка изображений коллекции. Поскольку все эти изображения
// загружаются через data-URIs, они будут загружены немедленно
$scope.images = [{
complete: false,
source: "1.png"
}, {
complete: false,
source: "2.png"
}, {
complete: false,
source: "3.png"
}];
// Пример статичного изображения
$scope.staticImage = {
complete: false,
source: "4.png"
};
});
// Выражение выполняется, когда текущее изображение будет загружено
Demo.directive( "bnLoad", function() {
// Связываем события DOM с областью видимости.
function link( $scope, element, attributes ) {
// Выражение выполняется в текущем цикле
// $digest и нет необходимости вызывать $apply()
function handleLoadSync() {
logWithPhase( "handleLoad - Sync" );
$scope.$eval( attributes.bnLoad );
}
// Выполнение выражения и последующий
// вызов $digest, чтобы Ангуляр смог узнать
// что изменение произошло
function handleLoadAsync() {
logWithPhase( "handleLoad - Async" );
$scope.$apply( function() {
handleLoadSync();
});
}
// Записываем в лог фазу жизненного цикла текущей области видимости.
function logWithPhase( message ) {
console.log( message, ":", $scope.$$phase );
}
// Проверяем, было ли изображение уже загружено.
// Если изображение взято из кэша браузера
// или загружено как Data URI,
// то не будет никаких задержек до окончания загрузки
if ( element[0].src && element[0].complete ) {
handleLoadSync();
// Изображение будет загружено в какой-то момент в будущем
// (т.е. асинхронно в связующей функции).
} else {
element.on( "load.bnLoad", handleLoadAsync );
}
// В демонстрационных целях давайте понаблюдаем за
// интерполированныеми атрибутами, чтобы увидеть
// в какой фазе находится область видимости
attributes.$observe("src", function( srcAttribute ) {
logWithPhase( "$observe : " + srcAttribute );
});
// В демонстрационных целях давайте понаблюдаем
// за изменениями значения изображения после окончания загрузки.
// Примечание: директива НЕ должна знать об этом значении модели;
// но мы ознакомимся здесь с жизненным циклом.
$scope.$watch("( image || staticImage ).complete", function( newValue ) {
logWithPhase( "$watch : " + newValue );
});
// Очищаем после уничтожения области видимости
$scope.$on("$destroy", function() {
element.off( "load.bnLoad" );
});
}
// Возвращаем конфигурацию директивы
return({
link: link,
restrict: "A"
});
}
);
</script>
</body>
</html>
Как можно видеть, три изображения (внутри ngRepeat) загружаются динамически и одно — статически. Загрузка всех четырех изображений мониторится с помощью директивы bnLoad и фазы цикла $digest записываются в лог.
Статическое изображение — 4.png — ко времени выполнения директивы будет уже загружено и послужит причиной первого срабатывания обработчика, который затем выведет в консоль следующее:
handleLoad - Sync : $apply
$observe : 4.png : $digest
$watch : true : $digest
$observe : 1.png : $digest
$watch : false : $digest
$observe : 2.png : $digest
$watch : false : $digest
$observe : 3.png : $digest
$watch : false : $digest
$observe : 1.png : $digest
$observe : 2.png : $digest
$observe : 3.png : $digest
handleLoad - Async : null
handleLoad - Sync : $apply
$watch : true : $digest
handleLoad - Async : null
handleLoad - Sync : $apply
$watch : true : $digest
handleLoad - Async : null
handleLoad - Sync : $apply
$watch : true : $digest
Знаю, что, непонятно с какой стороны подступиться, поэтому укажу несколько ключевых пунктов:
Первое срабатывание handleLoadSync() было вызвано статическим изображением. Обратите внимание, что оно произошло уже внутри фазы $apply, в которой Ангуляр проводит грязную проверку. Это потому, что она была вызвана в связующей функции link(), которая уже находится в контексте Ангуляра.
Все $observe() и $watch() обработчики находятся внутри фазы $digest грязной проверки жизненного цикла Ангуляра. Однажды попав туда, не нужно будет говорить Ангуляру о любых изменениях в $scope. Ангуляр будет автоматически выполнять грязную проверку после каждого цикла $digest.
Все изображения, загружаемые асинхронно, вызвали метод handleLoadAsync(), который использует метод $apply() для того, чтобы сообщить Ангуляру об этих изменениях. Именно поэтому все последующие методы handleLoadSync() находятся в фазе $apply — они были вызваны из обработчика handleLoadAsync().
Как упоминал ранее, директивы Ангуляра являются очень мощными, но с подвывертом и придется попыхтеть, чтобы поставить голову на место. Время выполнения в директиве Ангуляра имеет решающее значение для его функциональности, и это одна из наиболее трудных вещей для правильного понимания. Добавьте что-то вроде CSS-переходов — которые имеют только частичную поддержку браузерами — и вы быстро заметите, что проблемы с $digest в одном браузере возникают, а в другом — нет. Надеемся, что это исследование поможет немного лучше разобраться в причине подобных проблем.
This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers. Five Filters recommends: 'You Say What You Like, Because They Like What You Say' - http://www.medialens.org/index.php/alerts/alert-archive/alerts-2013/731-you-say-what-you-like-because-they-like-what-you-say.html
Комментариев нет:
Отправить комментарий