Почему используется if (! $Scope. $$ phase) $scope. $Apply() анти-шаблон?
Иногда мне нужно использовать $scope.$apply
в моем коде, и иногда он выдает ошибку "digest is in progress". Итак, я начал искать способ и нашел этот вопрос: AngularJS: Предотвращение ошибки $digest уже выполняется при вызове $scope. $Apply(). Однако в комментариях (и на wiki-странице angular) вы можете прочитать:
Не выполняйте if (! $scope. $$ phase) $scope. $apply(), это означает вашу область $. $apply() недостаточно высок в стеке вызовов.
Итак, у меня есть два вопроса:
- Почему именно это анти-шаблон?
- Как можно безопасно использовать $scope. $apply?
Еще одно "решение" для предотвращения ошибки "дайджест уже выполняется", похоже, использует $timeout:
$timeout(function() {
//...
});
Это путь? Это безопаснее? Итак, вот реальный вопрос: как я могу полностью устранить возможность ошибки "дайджест уже в процессе"?
PS: Я использую $scope. $применяются в не-угловых обратных вызовах, которые не являются синхронными. (насколько я знаю, это ситуации, когда вы должны использовать $scope. $apply, если вы хотите, чтобы ваши изменения были применены)
Ответы
Ответ 1
После некоторого дополнительного копания я смог решить вопрос, всегда ли безопасно использовать $scope.$apply
. Короткий ответ - да.
Длинный ответ:
Из-за того, как ваш браузер выполняет Javascript, невозможно, чтобы два вызова дайджеста сталкивались случайно.
Код JavaScript, который мы пишем, не запускается за один раз, а выполняется по очереди. Каждый из этих поворотов работает без передышки от начала до конца, а когда очередь работает, в нашем браузере больше ничего не происходит. (от http://jimhoskins.com/2012/12/17/angularjs-and-apply.html)
Следовательно, ошибка "дайджест уже выполняется" может произойти только в одной ситуации: когда $apply выдается внутри другого $apply, например:
$scope.apply(function() {
// some code...
$scope.apply(function() { ... });
});
В этой ситуации может возникнуть не , если мы используем $scope.apply в чистом обращении без учета углов, например, обратный вызов 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
правильно: в чистом js-обратном вызове, например setTimeout
.
Подробнее читайте http://jimhoskins.com/2012/12/17/angularjs-and-apply.html.
Ответ 2
В любом случае, когда ваш дайджест выполняется, и вы нажимаете другую услугу для переваривания, она просто дает ошибку, то есть дайджест уже выполняется.
поэтому, чтобы вылечить это, у вас есть два варианта.
вы можете проверить, есть ли какой-либо другой дайджест, как опрос.
Первый
if ($scope.$root.$$phase != '$apply' && $scope.$root.$$phase != '$digest') {
$scope.$apply();
}
если указанное выше условие истинно, тогда вы можете применить свою область $. $apply otherwies not и
второе решение использует $timeout
$timeout(function() {
//...
})
он не позволит другому дайджесту начинаться до истечения $timeout завершения выполнения.
Ответ 3
Это определенно анти-шаблон сейчас. Я видел, как дайджест взорвался, даже если вы проверяете фазу $$. Вы просто не должны обращаться к внутреннему API, обозначенному префиксами $$
.
Вы должны использовать
$scope.$evalAsync();
так как это предпочтительный метод в Angular ^ 1.4 и специально показан как API для прикладного уровня.
Ответ 4
scope.$apply
запускает цикл $digest
, который является основополагающим для привязки двухсторонних данных
A $digest
проверяет цикл для объектов, т.е. модели (точнее, $watch
), прикрепленные к $scope
, чтобы оценить, изменились ли их значения, и если он обнаруживает изменение, то он принимает необходимые шаги для обновления представления.
Теперь, когда вы используете $scope.$apply
, вы сталкиваетесь с ошибкой "Уже в процессе" , поэтому вполне очевидно, что работает $digest, но что вызвало его?
ans → каждый вызов $http
, все ng-click, repeat, show, hide etc запускают цикл $digest
И НАСТОЯЩАЯ ЧАСТЬ, ЧТОБЫ ИСПОЛЬЗУЕТЕ КАЖДОЙ $SCOPE.
Например, ваша страница имеет 4 контроллера или директивы A, B, C, D
Если у вас есть 4 $scope
свойства в каждом из них, то у вас на вашей странице есть все свойства области $16.
Если вы запускаете $scope.$apply
в контроллере D, тогда цикл $digest
будет проверять все 16 значений!!! плюс все свойства $rootScope.
Ответ → , но $scope.$digest
запускает a $digest
для дочернего и одного объекта, поэтому он будет проверять только 4 свойства. Поэтому, если вы уверены, что изменения в D не повлияют на A, B, C, используйте $scope.$diges
t not $scope.$apply
.
Таким образом, простой ng-click или ng-show/hide может запускать цикл $digest
для более чем 100 свойств, даже если у пользователя не было никакого события!
Ответ 5
Используйте $timeout
, это рекомендуется.
Мой сценарий заключается в том, что мне нужно изменить элементы на странице, основываясь на данных, полученных из WebSocket. И поскольку он находится вне Angular, без $timeout, только модель будет изменена, но не вид. Поскольку Angular не знает, что часть данных была изменена. $timeout
в основном говорит Angular, чтобы внести изменения в следующий раунд $digest.
Я тоже пробовал следующее: он работает. Разница в том, что $timeout более ясный.
setTimeout(function(){
$scope.$apply(function(){
// changes
});
},0)
Ответ 6
Я нашел очень классное решение:
.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
});
}
])