Если вы уже создавали свои собственные директивы, можно не сомневаться, что видели одно из двух сообщений:
$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 в одном браузере возникают, а в другом — нет. Надеемся, что это исследование поможет немного лучше разобраться в причине подобных проблем.
ссылка на оригинал статьи http://habrahabr.ru/post/188760/
Добавить комментарий