Многие ответы здесь содержат полезные советы, но также могут привести к путанице. Простое использование $timeout
- не лучшее и не правильное решение. Кроме того, обязательно прочитайте это, если вас беспокоит производительность или масштабируемость.
Вещи, которые вы должны знать
$$phase
является частной структурой, и для этого есть веские причины.
$timeout(callback)
будет ждать, пока текущий цикл дайджеста (если есть) будет выполнен, затем выполнить обратный вызов, а затем выполнить в конце полный $apply
.
$timeout(callback, delay, false)
будет делать то же самое (с необязательной задержкой перед выполнением обратного вызова), но не будет запускать $apply
(третий аргумент), который сохраняет производительность, если вы не изменили свою угловую модель ($ scope).
$scope.$apply(callback)
вызывает, среди прочего, $rootScope.$digest
что означает, что он перенаправит корневую область приложения и всех его дочерних элементов, даже если вы находитесь в изолированной области.
$scope.$digest()
просто синхронизирует свою модель с представлением, но не будет переваривать родительскую область видимости, что может сэкономить много производительности при работе с изолированной частью вашего HTML с изолированной областью (в основном из директивы). $ digest не принимает обратный вызов: вы выполняете код, а затем перевариваете.
$scope.$evalAsync(callback)
был представлен в angularjs 1.2 и, вероятно, решит большинство ваших проблем. Пожалуйста, обратитесь к последнему абзацу, чтобы узнать больше об этом.
если вы получаете $digest already in progress error
, то ваша архитектура неверна: либо вам не нужно повторно перенаправлять вашу область, либо вы не должны отвечать за это (см. ниже).
Как структурировать свой код
Когда вы получаете эту ошибку, вы пытаетесь переварить вашу область, пока она уже выполняется: поскольку вы не знаете состояние вашей области на тот момент, вы не отвечаете за ее обработку.
function editModel() {
$scope.someVar = someVal;
/* Do not apply your scope here since we don't know if that
function is called synchronously from Angular or from an
asynchronous code */
}
// Processed by Angular, for instance called by a ng-click directive
$scope.applyModelSynchronously = function() {
// No need to digest
editModel();
}
// Any kind of asynchronous code, for instance a server request
callServer(function() {
/* That code is not watched nor digested by Angular, thus we
can safely $apply it */
$scope.$apply(editModel);
});
И если вы знаете, что делаете и работаете над изолированной небольшой директивой, будучи частью большого приложения Angular, вы можете предпочесть $ digest вместо $ apply для сохранения производительности.
Обновление с Angularjs 1.2
Новый мощный метод был добавлен к любой области $: $evalAsync
. По сути, он выполнит свой обратный вызов в текущем цикле дайджеста, если он происходит, в противном случае новый цикл дайджеста начнет выполнять обратный вызов.
Это все еще не так хорошо, как $scope.$digest
если вы действительно знаете, что вам нужно синхронизировать только изолированную часть вашего HTML (так как новый $apply
будет запущен, если ни один не выполняется), но это лучшее решение, когда вы выполняете функцию который вы не можете знать, будет ли он выполняться синхронно или нет , например, после извлечения потенциально кэшированного ресурса: иногда это потребует асинхронного вызова к серверу, в противном случае ресурс будет извлекаться локально синхронно.
В этих случаях и во всех других, где у вас был !$scope.$$phase
, обязательно используйте$scope.$evalAsync( callback )