AngularJS: Предотвращение ошибки $ digest, которая уже выполняется при вызове $ scope. $ Apply ()

838

Я обнаружил, что мне нужно все больше и больше вручную обновлять свою страницу до объема, начиная с создания приложения в угловом формате.

Единственный способ, которым я знаю, - это звонить $apply()из области действия моих контроллеров и директив. Проблема в том, что он выдает ошибку в консоль, которая гласит:

Ошибка: $ digest уже выполняется

Кто-нибудь знает, как избежать этой ошибки или добиться того же, но другим способом?

Lightbulb1
источник
34
Это действительно разочаровывает, что нам нужно использовать $ apply все больше и больше.
OZ_
Я также получаю эту ошибку, даже если я вызываю $ apply в обратном вызове. Я использую стороннюю библиотеку для доступа к данным на своих серверах, поэтому я не могу воспользоваться ни $ http, ни желать, поскольку мне пришлось бы переписать их библиотеку для использования $ http.
Тревор
45
использование$timeout()
Онур Йылдырым
6
используйте $ timeout (fn) + 1, это может решить проблему,! $ scope. $$ phase - не лучшее решение.
Хуэй Тан
1
Только оберните объем кода / вызов. $ Применять из в таймаутах (не $ таймаута) AJAX функции (не $ HTTP) и события (не ng-*). Убедитесь, что если вы вызываете его из функции (которая вызывается через timeout / ajax / events), то она также изначально не запускается под нагрузкой.
Патрик

Ответы:

660

Не используйте этот шаблон - это приведет к большему количеству ошибок, чем решит. Даже если вы думаете, что это что-то исправило, это не так.

Вы можете проверить, если $digestуже выполняется, проверив $scope.$$phase.

if(!$scope.$$phase) {
  //$digest or $apply
}

$scope.$$phaseвернется "$digest"или "$apply"если $digestили $applyв процессе. Я полагаю, что различие между этими состояниями заключается в том, что $digestбудут обрабатываться часы текущего объема и его дочерних $applyэлементов , и будут обрабатываться наблюдатели всех областей.

К слову, @ dnc253, если вы звоните $digestили $applyчасто звоните , возможно, вы делаете это неправильно. Я обычно нахожу, что мне нужно переварить, когда мне нужно обновить состояние области видимости в результате события DOM, запускаемого вне досягаемости Angular. Например, когда модал начальной загрузки Twitter становится скрытым. Иногда событие DOM срабатывает, когда выполняется a $digest, иногда нет. Вот почему я использую этот чек.

Я хотел бы узнать лучший способ, если кто-нибудь знает один.


Из комментариев: @anddoutoi

angular.js Anti Patterns

  1. Не делайте if (!$scope.$$phase) $scope.$apply(), это означает, что вы $scope.$apply()недостаточно высоко в стеке вызовов.
подветренный
источник
230
Мне кажется, что $ digest / $ apply должен делать это по умолчанию
Roy Truelove
21
Обратите внимание, что в некоторых случаях я должен проверить, но текущую область и корневую область. Я получаю значение для $$ phase в корне, но не в моей области. Думаю, это как-то связано с изолированной областью действия директивы, но ..
Roy Truelove
106
"Хватит делать if (!$scope.$$phase) $scope.$apply()", github.com/angular/angular.js/wiki/Anti-Patterns
anddoutoi
34
@anddoutoi: Согласен; Ваша ссылка ясно дает понять, что это не решение; Тем не менее, я не уверен, что подразумевается под "вы недостаточно высоки в стеке вызовов". Ты знаешь что это значит?
Тревор
13
@threed: смотрите ответ от aaronfrost. Правильный способ - использовать defer для запуска дайджеста в следующем цикле. В противном случае событие будет потеряно и не обновит область вообще.
Марек
663

Из недавней дискуссии с ребятами из Angular на эту тему: по соображениям будущего не следует использовать$$phase

Когда нажата для «правильного» способа сделать, ответ в настоящее время

$timeout(function() {
  // anything you want can go here and will safely be run on the next digest.
})

Недавно я столкнулся с этим, когда писал угловые сервисы для обёртывания API Facebook, Google и Twitter, в которых, в той или иной степени, передавались обратные вызовы.

Вот пример из службы. (Ради краткости, остальная часть службы - которая устанавливает переменные, вводит $ timeout и т. Д. - была отключена.)

window.gapi.client.load('oauth2', 'v2', function() {
    var request = window.gapi.client.oauth2.userinfo.get();
    request.execute(function(response) {
        // This happens outside of angular land, so wrap it in a timeout 
        // with an implied apply and blammo, we're in action.
        $timeout(function() {
            if(typeof(response['error']) !== 'undefined'){
                // If the google api sent us an error, reject the promise.
                deferred.reject(response);
            }else{
                // Resolve the promise with the whole response if ok.
                deferred.resolve(response);
            }
        });
    });
});

Обратите внимание, что аргумент задержки для $ timeout является необязательным и будет по умолчанию равен 0, если не установлен ( $ timeout вызывает $ browser.defer, который умолчанию равен 0, если задержка не установлена )

Немного не интуитивно понятно, но это ответ парней, пишущих на Angular, так что для меня этого достаточно!

betaorbust
источник
5
Я сталкивался с этим много раз в моих директивах. Писал один для редактора, и это оказалось отлично работает. Я был на встрече с Брэдом Грином, и он сказал, что Angular 2.0 будет огромным без цикла дайджеста, используя встроенную способность наблюдения JS и использование полифилла для браузеров, в которых его нет. На этом этапе нам больше не нужно это делать. :)
Майкл Дж. Калкинс
Вчера я видел проблему, когда вызов selectize.refreshItems () внутри $ timeout вызвал страшную ошибку рекурсивного дайджеста. Есть идеи, как это может быть?
iwein
3
Если вы используете $timeoutвместо нативной setTimeout, почему вы не используете $windowвместо нативной window?
LeeGee
2
@LeeGee: Смысл использования $timeoutв этом случае заключается в том, чтобы $timeoutобеспечить правильное обновление угловой области. Если $ digest не выполняется, это приведет к запуску нового $ digest.
благоговение
2
@webicy Это не вещь. Когда тело функции, переданной в $ timeout, запускается, обещание уже выполнено! Там нет абсолютно никаких причин для cancelэтого. Из документов : «В результате этого обещание будет выполнено с отказом». Вы не можете разрешить решенное обещание. Ваша отмена не вызовет никаких ошибок, но она также не принесет ничего хорошего.
daemonexmachina
324

Цикл дайджеста - это синхронный вызов. Это не даст контроль над циклом событий браузера, пока это не будет сделано. Есть несколько способов справиться с этим. Самый простой способ справиться с этим - использовать встроенный тайм-аут в $ timeout, а во-вторых, если вы используете подчеркивание или lodash (и вам следует), вызовите следующее:

$timeout(function(){
    //any code in here will automatically have an apply run afterwards
});

или если у вас есть lodash:

_.defer(function(){$scope.$apply();});

Мы попробовали несколько обходных путей и ненавидели внедрять $ rootScope во все наши контроллеры, директивы и даже некоторые фабрики. Итак, $ timeout и _.defer были нашими любимыми до сих пор. Эти методы успешно сообщают angular ждать следующего цикла анимации, что гарантирует завершение текущей области видимости. $ Apply.

морозный
источник
2
Это сопоставимо с использованием $ timeout (...)? Я использовал $ timeout в нескольких случаях, чтобы отложить до следующего цикла событий, и это, кажется, работает нормально - кто-нибудь знает, есть ли причина не использовать $ timeout?
Тревор
9
Это действительно должно использоваться, только если вы уже используете underscore.js. Это решение не стоит импортировать всю библиотеку подчеркивания только для того, чтобы использовать ее deferфункцию. Я предпочитаю $timeoutрешение, потому что у всех уже есть доступ $timeoutчерез angular, без каких-либо зависимостей от других библиотек.
теннисный агент
10
Верно ... но если вы не используете подчеркивание или lodash ... вам нужно пересмотреть то, что вы делаете. Эти две библиотеки изменили внешний вид кода.
морозно
2
У нас есть lodash как зависимость для Restangular (мы собираемся устранить Restangular в пользу ng-route в ближайшее время). Я думаю, что это хороший ответ, но не стоит полагать, что люди хотят использовать подчеркивание / lodash. Конечно, эти библиотеки в порядке ... если вы используете их достаточно ... в настоящее время я использую методы ES5, которые уничтожают 98% причин, по которым я использовал подчеркивание.
BradGreens
2
Вы правы @SgtPooki. Я изменил ответ, чтобы включить возможность использовать $ timeout. $ timeout и _.defer будут ждать следующего цикла анимации, что обеспечит завершение текущей области. $ apply. Спасибо за то, что держали меня честным и заставили меня обновить ответ здесь.
морозное
267

Многие ответы здесь содержат полезные советы, но также могут привести к путанице. Простое использование $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 )

floribon
источник
4
$timeoutкритикуется мимоходом. Можете ли вы дать больше причин, чтобы избежать $timeout?
mlhDev
88

Удобный маленький вспомогательный метод, чтобы сохранить этот процесс СУХИМ:

function safeApply(scope, fn) {
    (scope.$$phase || scope.$root.$$phase) ? fn() : scope.$apply(fn);
}
lambinator
источник
6
Ваше безопасное приложение помогло мне понять, что происходит намного больше, чем что-либо еще. Спасибо за публикацию этого.
Джейсон Море
4
Я собирался сделать то же самое, но разве это не значит, что есть шанс, что изменения, которые мы сделаем в fn (), не будут видны $ digest? Разве не было бы лучше отложить функцию, предполагая область действия. $$ phase === '$ digest'?
Спенсер Алджер
Я согласен, иногда $ apply () используется для запуска дайджеста, просто вызывая fn сам по себе ... не приведет ли это к проблеме?
CMCDragonkai
1
Я чувствую, что scope.$apply(fn);должно быть, scope.$apply(fn());потому что fn () будет выполнять функцию, а не fn. Пожалуйста, помогите мне, где я не прав
madhu131313
1
@ZenOut Вызов $ apply поддерживает множество различных типов аргументов, включая функции. Если передана функция, она оценивает функцию.
boxmein
33

У меня была такая же проблема со сторонними скриптами, такими как CodeMirror, например, и Krpano, и даже использование методов safeApply, упомянутых здесь, не решило ошибку для меня.

Но что решило, так это использование службы $ timeout (не забудьте сначала ввести ее).

Таким образом, что-то вроде:

$timeout(function() {
  // run my code safely here
})

и если внутри вашего кода вы используете

это

возможно, потому что он находится внутри контроллера фабричной директивы или просто нуждается в какой-то привязке, тогда вы бы сделали что-то вроде:

.factory('myClass', [
  '$timeout',
  function($timeout) {

    var myClass = function() {};

    myClass.prototype.surprise = function() {
      // Do something suprising! :D
    };

    myClass.prototype.beAmazing = function() {
      // Here 'this' referes to the current instance of myClass

      $timeout(angular.bind(this, function() {
          // Run my code safely here and this is not undefined but
          // the same as outside of this anonymous function
          this.surprise();
       }));
    }

    return new myClass();

  }]
)
Ciul
источник
32

Смотрите http://docs.angularjs.org/error/$rootScope:inprog

Проблема возникает, когда у вас есть вызов, $applyкоторый иногда выполняется асинхронно вне кода Angular (когда следует использовать $ apply), а иногда и синхронно внутри кода Angular (что приводит к $digest already in progressошибке).

Это может произойти, например, когда у вас есть библиотека, которая асинхронно выбирает элементы с сервера и кэширует их. При первом запросе элемента он будет извлечен асинхронно, чтобы не блокировать выполнение кода. Однако во второй раз элемент уже находится в кэше, поэтому его можно получить синхронно.

Способ предотвратить эту ошибку - убедиться, что вызываемый код $applyвыполняется асинхронно. Это можно сделать, запустив ваш код в вызове $timeoutс задержкой 0(по умолчанию). Однако вызов вашего кода внутри $timeoutустраняет необходимость вызова $apply, потому что $ timeout вызовет другой$digest цикл, который, в свою очередь, выполнит все необходимые обновления и т. Д.

Решение

Короче, вместо этого:

... your controller code...

$http.get('some/url', function(data){
    $scope.$apply(function(){
        $scope.mydate = data.mydata;
    });
});

... more of your controller code...

сделай это:

... your controller code...

$http.get('some/url', function(data){
    $timeout(function(){
        $scope.mydate = data.mydata;
    });
});

... more of your controller code...

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

Если кто-то не осознает какой-то существенный недостаток использования $timeoutover $apply, я не понимаю, почему вы не всегда могли бы использовать $timeout(с нулевой задержкой) вместо этого $apply, поскольку это будет делать примерно то же самое.

Тревор
источник
Спасибо, это сработало для моего случая, когда я не звоню $applyсам, но все еще получаю ошибку.
ariscris
5
Основным отличием является то, что $applyсинхронный (его обратный вызов выполняется, затем код, следующий за $ apply), в то время как $timeoutэто не так: выполняется текущий код, следующий за тайм-аутом, затем новый стек начинается с обратного вызова, как если бы вы использовали setTimeout. Это может привести к графическим сбоям, если вы обновляете дважды одну и ту же модель: $timeoutдождитесь обновления представления, прежде чем обновлять его снова.
флорибон
Спасибо, правда. У меня был метод, вызванный в результате некоторой активности $ watch, и я пытался обновить пользовательский интерфейс до того, как мой внешний фильтр завершил работу. Помещение этого в функцию $ timeout работало для меня.
Джмаркетт
28

Когда вы получаете эту ошибку, это в основном означает, что она уже находится в процессе обновления вашего представления. Вам действительно не нужно звонить $apply()внутри вашего контроллера. Если ваше представление обновляется не так, как вы ожидаете, и после вызова появляется эта ошибка $apply(), скорее всего, это означает, что вы не обновляете модель правильно. Если вы опубликуете некоторые подробности, мы могли бы выяснить основную проблему.

dnc253
источник
хех, я потратил целый день, чтобы выяснить, что AngularJS просто не может смотреть привязки "волшебным образом", и я должен иногда толкать его с помощью $ apply ().
OZ_
что вообще значит you're not updating the the model correctly? $scope.err_message = 'err message';не правильное обновление?
OZ_
2
Единственный раз, когда вам нужно позвонить, $apply()это когда вы обновляете модель "снаружи" angular (например, из плагина jQuery). Легко попасть в ловушку вида, который выглядит неправильно, поэтому вы бросаете кучу $apply()s повсюду, что в итоге приводит к ошибке, замеченной в OP. Когда я говорю, you're not updating the the model correctlyя просто имею в виду, что вся бизнес-логика неправильно заполняет все, что может находиться в области видимости, что приводит к тому, что представление выглядит не так, как ожидалось.
dnc253
@ dnc253 Я согласен, и я написал ответ. Зная, что я знаю сейчас, я бы использовал $ timeout (function () {...}); Он делает то же самое, что и _.defer. Они оба переходят к следующему циклу анимации.
морозное
14

Самая короткая форма сейфа $applyэто:

$timeout(angular.noop)
Чернокнижник
источник
11

Вы также можете использовать evalAsync. Он будет запущен через некоторое время после завершения дайджеста!

scope.evalAsync(function(scope){
    //use the scope...
});
CMCDragonkai
источник
10

Прежде всего, не исправляйте это так

if ( ! $scope.$$phase) { 
  $scope.$apply(); 
}

Это не имеет смысла, потому что $ phase - это просто логический флаг цикла $ digest, поэтому ваш $ apply () иногда не запускается. И помните, что это плохая практика.

Вместо этого используйте $timeout

    $timeout(function(){ 
  // Any code in here will automatically have an $scope.apply() run afterwards 
$scope.myvar = newValue; 
  // And it just works! 
});

Если вы используете подчеркивание или lodash, вы можете использовать defer ():

_.defer(function(){ 
  $scope.$apply(); 
});
Сагар М
источник
9

Иногда вы все равно будете получать ошибки, если будете использовать этот способ ( https://stackoverflow.com/a/12859093/801426 ).

Попробуй это:

if(! $rootScope.$root.$$phase) {
...
bullgare
источник
5
использование обоих! $ scope. $$ phase и! $ scope. $ root. $$ phase (not! $ rootScope. $ root. $$ phase) работает для меня. +1
asprotte
2
$rootScopeи anyScope.$rootтот же парень. $rootScope.$rootизбыточно
Флорибон
5

попробуйте использовать

$scope.applyAsync(function() {
    // your code
});

вместо

if(!$scope.$$phase) {
  //$digest or $apply
}

$ applyAsync Запланировать вызов $ apply для более позднего времени. Это может быть использовано для постановки в очередь нескольких выражений, которые должны оцениваться в одном и том же дайджесте.

ПРИМЕЧАНИЕ. В $ digest $ applyAsync () будет сбрасываться только в том случае, если текущая область действия - $ rootScope. Это означает, что если вы вызовете $ digest для дочерней области, она не будет неявно очищать очередь $ applyAsync ().

Exmaple:

  $scope.$applyAsync(function () {
                if (!authService.authenticated) {
                    return;
                }

                if (vm.file !== null) {
                    loadService.setState(SignWizardStates.SIGN);
                } else {
                    loadService.setState(SignWizardStates.UPLOAD_FILE);
                }
            });

Ссылки:

1. Scope. $ ApplyAsync () против Scope. $ EvalAsync () в AngularJS 1.3

  1. AngularJs Docs
Эдуардо Эляйек
источник
4

Я бы посоветовал вам использовать пользовательское событие, а не запускать цикл дайджеста.

Я пришел к выводу, что трансляция пользовательских событий и регистрация слушателей этих событий является хорошим решением для запуска действия, которое вы хотите выполнить независимо от того, находитесь ли вы в цикле дайджеста.

Создавая пользовательское событие, вы также более эффективно работаете со своим кодом, потому что вы только запускаете прослушиватели, подписанные на указанное событие, а НЕ запускаете все наблюдения, связанные с областью действия, как если бы вы вызывали область действия. $ Apply.

$scope.$on('customEventName', function (optionalCustomEventArguments) {
   //TODO: Respond to event
});


$scope.$broadcast('customEventName', optionalCustomEventArguments);
nelsonomuto
источник
3

yearofmoo проделал большую работу по созданию для нас многократно используемой функции $ safeApply:

https://github.com/yearofmoo/AngularJS-Scope.SafeApply

Применение :

//use by itself
$scope.$safeApply();

//tell it which scope to update
$scope.$safeApply($scope);
$scope.$safeApply($anotherScope);

//pass in an update function that gets called when the digest is going on...
$scope.$safeApply(function() {

});

//pass in both a scope and a function
$scope.$safeApply($anotherScope,function() {

});

//call it on the rootScope
$rootScope.$safeApply();
$rootScope.$safeApply($rootScope);
$rootScope.$safeApply($scope);
$rootScope.$safeApply($scope, fn);
$rootScope.$safeApply(fn);
RNobel
источник
2

Я смог решить эту проблему, вызывая $evalвместо $applyмест, где я знаю, что $digestфункция будет работать.

Согласно документации , в $applyосновном это:

function $apply(expr) {
  try {
    return $eval(expr);
  } catch (e) {
    $exceptionHandler(e);
  } finally {
    $root.$digest();
  }
}

В моем случае, ng-clickизменение переменной в области видимости, и $ watch этой переменной изменяет другие переменные, которые должны быть $applied. Этот последний шаг вызывает ошибку "дайджест уже выполняется".

Заменив $applyна$eval внутри выражения наблюдения переменные области видимости обновляются, как и ожидалось.

Следовательно, похоже, что если дайджест будет запущен в любом случае из-за каких-то других изменений в Angular, то $evalэто все, что вам нужно сделать.

teleclimber
источник
2

использовать $scope.$$phase || $scope.$apply();вместо

Висах Б Суджатан
источник
1

Понимание того, что угловые документы вызова проверки $$phaseв антишаблон , я пытался получить $timeoutи _.deferработать.

Тайм- аут и отложенные методы создают вспышку непарсированного {{myVar}}контента в домене, как FOUT . Для меня это было неприемлемо. Мне не нужно много догматично говорить, что что-то взломано, и у меня нет подходящей альтернативы.

Единственное, что работает каждый раз:

if(scope.$$phase !== '$digest'){ scope.$digest() },

Я не понимаю опасности этого метода, или почему его описывают как взломать люди в комментариях и ангулярной команде. Команда кажется точной и легко читаемой:

«Сделайте дайджест, если он уже не происходит»

В CoffeeScript это еще красивее:

scope.$digest() unless scope.$$phase is '$digest'

В чем проблема с этим? Есть ли альтернатива, которая не создаст FOUT? $ safeApply выглядит хорошо, но также использует $$phaseметод проверки.

SimplGy
источник
1
Я хотел бы видеть информированный ответ на этот вопрос!
Бен Уилер,
Это хак, потому что это означает, что вы пропускаете контекст или не понимаете код в этой точке: либо вы находитесь в цикле углового дайджеста и вам это не нужно, либо вы асинхронно вне этого, а затем вам это нужно. Если вы не можете знать, что в этом пункте кода, то вы не несете ответственности за его усвоение
floribon
1

Это мой сервис утилит:

angular.module('myApp', []).service('Utils', function Utils($timeout) {
    var Super = this;

    this.doWhenReady = function(scope, callback, args) {
        if(!scope.$$phase) {
            if (args instanceof Array)
                callback.apply(scope, Array.prototype.slice.call(args))
            else
                callback();
        }
        else {
            $timeout(function() {
                Super.doWhenReady(scope, callback, args);
            }, 250);
        }
    };
});

и это пример его использования:

angular.module('myApp').controller('MyCtrl', function ($scope, Utils) {
    $scope.foo = function() {
        // some code here . . .
    };

    Utils.doWhenReady($scope, $scope.foo);

    $scope.fooWithParams = function(p1, p2) {
        // some code here . . .
    };

    Utils.doWhenReady($scope, $scope.fooWithParams, ['value1', 'value2']);
};
ranbuch
источник
1

Я использовал этот метод, и он, кажется, работает отлично. Это просто ждет времени, когда цикл закончится, а затем запускается apply(). Просто вызовите функцию apply(<your scope>)из любой точки мира.

function apply(scope) {
  if (!scope.$$phase && !scope.$root.$$phase) {
    scope.$apply();
    console.log("Scope Apply Done !!");
  } 
  else {
    console.log("Scheduling Apply after 200ms digest cycle already in progress");
    setTimeout(function() {
        apply(scope)
    }, 200);
  }
}
Ашу
источник
1

Когда я отключил отладчик, ошибка больше не происходит. В моем случае это произошло из-за того, что отладчик остановил выполнение кода.

jmojico
источник
0

похоже на ответы выше, но это сработало для меня ... в сервис добавить:

    //sometimes you need to refresh scope, use this to prevent conflict
    this.applyAsNeeded = function (scope) {
        if (!scope.$$phase) {
            scope.$apply();
        }
    };
Шон Дотей
источник
0

Ты можешь использовать

$timeout

чтобы предотвратить ошибку.

 $timeout(function () {
                        var scope = angular.element($("#myController")).scope();
                        scope.myMethod();
                        scope.$scope();
                    },1);
Сатиш Сингх
источник
Что если я не хочу использовать $ timeout
rahim.nagori
0

Проблема в основном наступает, когда мы просим angular запустить цикл дайджеста, даже если он находится в процессе, что создает проблему в angular to понимание. исключение следствия в консоли.
1. Не имеет смысла вызывать scope. $ Apply () внутри функции $ timeout, потому что внутренне это делает то же самое.
2. Код идет с ванильной функцией JavaScript, потому что определен ее собственный не угловой угол, т.е. setTimeout.
3. Для этого вы можете использовать

if (!
Scope . $$ phase) { scope. $ EvalAsync (function () {

}); }

Сачин Мишра
источник
0
        let $timeoutPromise = null;
        $timeout.cancel($timeoutPromise);
        $timeoutPromise = $timeout(() => {
            $scope.$digest();
        }, 0, false);

Вот хорошее решение, чтобы избежать этой ошибки и избежать $ apply

Вы можете комбинировать это с debounce (0), если вызов основан на внешнем событии. Выше «debounce», который мы используем, и полный пример кода

.factory('debounce', [
    '$timeout',
    function ($timeout) {

        return function (func, wait, apply) {
            // apply default is true for $timeout
            if (apply !== false) {
                apply = true;
            }

            var promise;
            return function () {
                var cntx = this,
                    args = arguments;
                $timeout.cancel(promise);
                promise = $timeout(function () {
                    return func.apply(cntx, args);
                }, wait, apply);
                return promise;
            };
        };
    }
])

и сам код для прослушивания какого-либо события и вызова $ digest только в нужной вам области видимости

        let $timeoutPromise = null;
        let $update = debounce(function () {
            $timeout.cancel($timeoutPromise);
            $timeoutPromise = $timeout(() => {
                $scope.$digest();
            }, 0, false);
        }, 0, false);

        let $unwatchModelChanges = $scope.$root.$on('updatePropertiesInspector', function () {
            $update();
        });


        $scope.$on('$destroy', () => {
            $timeout.cancel($update);
            $timeout.cancel($timeoutPromise);
            $unwatchModelChanges();
        });
Сергей Саакян
источник
-3

Нашел это: https://coderwall.com/p/ngisma, где Натан Уокер (в нижней части страницы) предлагает декоратор в $ rootScope для создания func 'safeApply', код:

yourAwesomeModule.config([
  '$provide', function($provide) {
    return $provide.decorator('$rootScope', [
      '$delegate', function($delegate) {
        $delegate.safeApply = function(fn) {
          var phase = $delegate.$$phase;
          if (phase === "$apply" || phase === "$digest") {
            if (fn && typeof fn === 'function') {
              fn();
            }
          } else {
            $delegate.$apply(fn);
          }
        };
        return $delegate;
      }
    ]);
  }
]);
Уоррен Дэвис
источник
-7

Это решит вашу проблему:

if(!$scope.$$phase) {
  //TODO
}
eebbesen
источник
Не делайте, если (! $ Scope. $$ phase) $ scope. $ Apply (), это означает, что ваш $ scope. $ Apply () недостаточно высок в стеке вызовов.
MGot90