Иногда мне нужно использовать $scope.$apply
в своем коде, а иногда возникает ошибка «дайджест уже выполняется». Итак, я начал искать способ обойти это и нашел следующий вопрос: AngularJS: Предотвратить уже выполняющийся дайджест ошибки при вызове $ scope. $ Apply () . Однако в комментариях (и на вики-странице angular) вы можете прочитать:
Не делайте if (! $ Scope. $$ phase) $ scope. $ Apply (), это означает, что ваша $ scope. $ Apply () недостаточно высока в стеке вызовов.
Итак, теперь у меня есть два вопроса:
- Почему именно это антипаттерн?
- Как я могу безопасно использовать $ scope. $ Apply?
Другое "решение" для предотвращения ошибки "дайджест уже выполняется", похоже, использует $ timeout:
$timeout(function() {
//...
});
Это путь? Это безопаснее? Итак, вот настоящий вопрос: как я могу полностью исключить возможность ошибки «дайджест уже выполняется»?
PS: Я использую только $ scope. $ Apply в обратных вызовах, отличных от angularjs, которые не являются синхронными. (насколько я знаю, это ситуации, когда вы должны использовать $ scope. $ apply, если хотите, чтобы ваши изменения были применены)
источник
scope
из angular изнутри или извне angular. Так что по этому вы всегда знаете, нужно вам звонитьscope.$apply
или нет. И если вы используете один и тот же код для угловых и неугловыхscope
манипуляций, вы делаете это неправильно, он всегда должен быть разделен ... поэтому в основном, если вы столкнетесь с ситуацией, когда вам нужно проверитьscope.$$phase
, ваш код не спроектирован правильно, и всегда есть способ сделать это «правильно»digest already in progress
Ответы:
Еще немного покопавшись, я смог решить вопрос, всегда ли его безопасно использовать
$scope.$apply
. Краткий ответ: да.Длинный ответ:
Из-за того, как ваш браузер выполняет Javascript, два вызова дайджеста не могут случайно столкнуться .
Следовательно, ошибка «дайджест уже выполняется» может возникнуть только в одной ситуации: когда $ apply выдается внутри другого $ apply, например:
$scope.apply(function() { // some code... $scope.apply(function() { ... }); });
Эта ситуация не может возникнуть, если мы используем $ scope.apply в чистом обратном вызове, отличном от angularjs, как, например, обратный вызов
setTimeout
. Таким образом, следующий код на 100% надежен, и нет необходимости делатьif (!$scope.$$phase) $scope.$apply()
setTimeout(function () { $scope.$apply(function () { $scope.message = "Timeout called!"; }); }, 2000);
даже этот безопасен:
$scope.$apply(function () { setTimeout(function () { $scope.$apply(function () { $scope.message = "Timeout called!"; }); }, 2000); });
Что НЕ безопасно (потому что $ timeout - как и все помощники angularjs - уже вызывает
$scope.$apply
вас):$timeout(function () { $scope.$apply(function () { $scope.message = "Timeout called!"; }); }, 2000);
Это также объясняет, почему использование
if (!$scope.$$phase) $scope.$apply()
является анти-шаблоном. Он вам просто не нужен, если вы используете$scope.$apply
его правильно: например, в чистом обратном вызове jssetTimeout
.Прочтите http://jimhoskins.com/2012/12/17/angularjs-and-apply.html для более подробного объяснения.
источник
$document.bind('keydown', function(e) { $rootScope.$apply(function() { // a passed through function from the controller gets executed here }); });
я действительно не знаю, почему я должен использовать здесь $ apply, потому что я использую $ document.bind ..function $DocumentProvider(){ this.$get = ['$window', function(window){ return jqLite(window.document); }]; }
там нет заявки.$timeout
семантически означает запуск кода после задержки. Это может быть функционально безопасно, но это взлом. Должен быть безопасный способ использования $ apply, когда вы не можете узнать, выполняется ли$digest
цикл или вы уже находитесь внутри$apply
.Сейчас это определенно антипаттерн. Я видел, как дайджест взорвался, даже если вы проверите фазу $$. Вы просто не должны иметь доступ к внутреннему API, обозначенному
$$
префиксами.Вы должны использовать
$scope.$evalAsync();
так как это предпочтительный метод в Angular ^ 1.4 и специально представлен как API для уровня приложения.
источник
В любом случае, когда ваш дайджест выполняется, и вы нажимаете на дайджест другую службу, он просто выдает ошибку, т.е. дайджест уже выполняется. так что вылечить это у вас есть два варианта. вы можете проверить любой другой дайджест, например опрос.
Первый
if ($scope.$root.$$phase != '$apply' && $scope.$root.$$phase != '$digest') { $scope.$apply(); }
если вышеупомянутое условие истинно, то вы можете применить свою $ scope. $ apply в противном случае и
Второе решение - использовать $ timeout
$timeout(function() { //... })
он не позволит запустить другой дайджест, пока $ timeout не завершит его выполнение.
источник
$scope.$apply();
.$timeout
это ключ! это работает, и позже я обнаружил, что это тоже рекомендуется.scope.$apply
запускает$digest
цикл, который является фундаментальным для двусторонней привязки данныхА
$digest
проверки цикла для объектов , то есть модели (чтобы быть точным$watch
) , присоединенные к$scope
оценивать , если их значения изменились , и если он обнаруживает изменения , то он предпринимает необходимые шаги , чтобы обновить представление.Теперь, когда вы используете,
$scope.$apply
вы сталкиваетесь с ошибкой «Уже выполняется», поэтому совершенно очевидно, что $ дайджест запущен, но что его вызвало?ans -> каждый
$http
вызов, все ng-click, повторение, отображение, скрытие и т. д. запускают$digest
цикл, И НАХУДШАЯ ЧАСТЬ ЗАПУСКАЕТ КАЖДЫЙ $ SCOPE.т.е. скажем, на вашей странице 4 контроллера или директивы A, B, C, D
Если у вас есть 4
$scope
свойства в каждом из них, то на вашей странице будет всего 16 свойств области действия.Если вы запускаете
$scope.$apply
контроллер D,$digest
цикл проверяет все 16 значений !!! плюс все свойства $ rootScope.Ответ -> но
$scope.$digest
запускает$digest
дочернюю и ту же область видимости, поэтому проверяет только 4 свойства. Поэтому, если вы уверены, что изменения в D не повлияют на A, B, C, тогда используйте$scope.$diges
t not$scope.$apply
.Таким образом, простой ng-click или ng-show / hide может запустить
$digest
цикл для более чем 100+ свойств, даже если пользователь не инициировал никакого события !источник
Используйте
$timeout
рекомендуемый способ.Мой сценарий заключается в том, что мне нужно изменить элементы на странице на основе данных, полученных от WebSocket. И поскольку он находится за пределами Angular, без тайм-аута $ будет изменена единственная модель, но не представление. Потому что Angular не знает, что часть данных была изменена.
$timeout
в основном говорит Angular внести изменения в следующий раунд $ digest.Я тоже пробовал следующее, и это работает. Для меня разница в том, что таймаут $ более понятен.
setTimeout(function(){ $scope.$apply(function(){ // changes }); },0)
источник
$http
). В противном случае вам придется повторять этот код повсюду.$scope.$apply
использоватьsetTimeout
или$timeout
Нашел очень крутое решение:
.factory('safeApply', [function($rootScope) { return function($scope, fn) { var phase = $scope.$root.$$phase; if (phase == '$apply' || phase == '$digest') { if (fn) { $scope.$eval(fn); } } else { if (fn) { $scope.$apply(fn); } else { $scope.$apply(); } } } }])
введите это там, где вам нужно:
.controller('MyCtrl', ['$scope', 'safeApply', function($scope, safeApply) { safeApply($scope); // no function passed in safeApply($scope, function() { // passing a function in }); } ])
источник