Angular $scope. $apply vs $timeout как безопасный $apply

Я пытаюсь лучше понять нюансы использования службы $timeout в Angular как своего рода метод "безопасный метод $apply". В основном в сценариях, где фрагмент кода может выполняться в ответ на событие Angular или событие без angular, такое как jQuery или какое-либо стандартное событие DOM.

Как я понимаю вещи:

  • Копирование кода в $scope. $apply отлично работает для сценариев, где вы еще не находятся в цикле дайджеста (событие jQuery), но будет вызывать ошибку, если выполняется дайджест.
  • Код обложки в вызове $timeout() без параметра задержки работает ли уже в цикле дайджест или нет

Посмотрев исходный код Angular, он выглядит так: $timeout делает вызов $rootScope. $apply().

  • Почему не $timeout() также вызывает ошибку, если цикл дайджеста уже выполняется?
  • Лучше всего использовать $scope. $apply(), если вы точно знаете, что дайджест не будет выполняться, и $timeout(), когда это необходимо для безопасности в любом случае?
  • Является ли $timeout() действительно приемлемым "безопасным", или есть ли gotchas?

Спасибо за понимание.

Ответ 1

Посмотрев исходный код Angular, он выглядит так: $timeout делает вызов $RootScope. $Применяются().

  • Почему не $timeout() также вызывает ошибку, если цикл дайджеста уже выполняется?

$timeout использует недокументированную службу Angular $browser. В частности, он использует $browser.defer(), который прерывает выполнение вашей функции асинхронно через window.setTimeout(fn, delay), который всегда будет работать за пределами Angular жизненного цикла. Только один раз window.setTimeout запустил вашу функцию, будет $timeout вызов $rootScope.$apply().

  • Лучше всего использовать $scope. $apply(), если вы точно знаете, что дайджест не будет выполняться, и $timeout(), когда это необходимо для безопасности в любом случае?

Я бы так сказал. Другим вариантом использования является то, что иногда вам нужно получить доступ к переменной $scope, которая, как вы знаете, будет инициализирована после дайджеста. Простым примером может быть, если вы хотите, чтобы состояние формы загрязнялось внутри вашего конструктора контроллера (по какой-либо причине). Без $timeout FormController не был инициализирован и опубликован в $scope, поэтому wrapping $scope.yourform.setDirty() внутри $timeout гарантирует, что FormController был инициализирован. Конечно, вы можете сделать все это с помощью директивы без $timeout, просто используя другой пример использования.

  • Является ли $timeout() действительно приемлемым "безопасным", или есть ли gotchas?

Это всегда должно быть безопасно, но ваш подход к методу всегда должен быть нацелен на $apply(), на мой взгляд. Текущее приложение Angular, над которым я работаю, довольно велико, и нам нужно было полагаться только на $timeout, а не $apply().

Ответ 2

Если мы используем $apply в приложении, мы можем получить уже выполненный Error: $digest. Это происходит потому, что один цикл $digest может запускаться одновременно. Мы можем разрешить его с помощью $timeout или $evalAsync.

Тайм-аут $не генерирует ошибку, как "$ digest уже выполняется", потому что $timeout сообщает Angular, что после текущего цикла ожидания ожидания ожидания ожидания и таким образом гарантируют, что не произойдет никаких столкновений между циклами дайджеста и, следовательно, выход $timeout будет выполняться в новом цикле $digest.

Я попытался их объяснить: Сравнение применения, таймаута, дайджеста и evalAsync.

Может быть, это поможет вам.

Ответ 3

Насколько я понимаю, $timeout представляет собой оболочку вокруг setTimeout, которая неявно вызывает $scope.$apply, что означает, что она выполняется за пределами жизненного цикла angular, но запускает сам жизненный цикл angular. Единственное, о чем я могу подумать, это то, что если вы ожидаете, что ваш результат будет доступен для этого $digest, вам нужно найти другой способ "безопасного применения" (который, AFAIK, доступен только через $scope.$$phase).