AngularJS: Запобігати дайджесту помилок $, який вже працює під час виклику $ range. $ Apply ()


838

Я знаходжу, що мені потрібно вручну оновлювати свою сторінку в моєму обсязі після створення програми в кутовому режимі.

Єдиний спосіб, з якого я це знаю, - це викликати $apply()сферу моїх контролерів та директив. Проблема в цьому полягає в тому, що він продовжує викидати помилку на консоль, яка говорить:

Помилка: дайджест $ вже триває

Хтось знає, як уникнути цієї помилки чи досягти тієї ж речі, але по-іншому?


34
Це дійсно страшно, що нам потрібно використовувати $ застосовувати все більше і більше.
OZ_

Я також отримую цю помилку, навіть незважаючи на те, що я закликаю $ Apply у зворотному дзвінку. Я використовую сторонні бібліотеки для доступу до даних на своїх серверах, тому я не можу скористатися $ http, а також не хочу, оскільки мені доведеться переписати їхню бібліотеку для використання $ http.
Тревор

45
використання$timeout()
Onur Yıldırım

6
використовуйте $ timeout (fn) + 1, Це може виправити проблему, $ $. Фаза $$ - не найкраще рішення.
Хуей Тан

1
Тільки оберніть обсяг коду / виклик. $ Застосовувати з в таймаут (не $ таймаута) AJAX функції (не $ HTTP) і події (НЕ ng-*). Переконайтесь, що якщо ви викликаєте його з функції (яка викликається через timeout / ajax / події), що вона спочатку також не працює при завантаженні.
Патрік

Відповіді:


660

Не використовуйте цю схему - це призведе до більшої кількості помилок, ніж вирішено. Незважаючи на те, що ви думаєте, що це щось виправляло, це не сталося.

Ви можете перевірити, чи a $digestвже працює, перевіривши $scope.$$phase.

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

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

До пункту @ dnc253, якщо ви опиняєтесь $digestабо дзвонили $applyчасто, ви можете робити це неправильно. Як правило, мені здається, що мені потрібно переварити, коли мені потрібно оновити стан області в результаті події DOM, що вистрілюється за межі досяжності Angular. Наприклад, коли модальний завантажувальний механізм щебетання стає прихованим. Іноді подія DOM спрацьовує, коли a $digestпрацює, іноді ні. Ось чому я використовую цю перевірку.

Я хотів би знати кращий спосіб, якщо хтось його знає.


З коментарів: автор @anddoutoi

angular.js Anti Patterns

  1. Не робіть if (!$scope.$$phase) $scope.$apply()це, це означає, що ваш $scope.$apply()недостатньо високий у стеку викликів.

230
Мені здається, що $ digest / $ apply слід зробити це за замовчуванням
Roy Truelove

21
Зауважте, що в деяких випадках я маю перевірити, але поточну область І кореневу область. Я отримував значення для $$ фази в корені, але не в моєму обсязі. Подумайте, це має щось спільне з ізольованою сферою директиви, але ..
Roy Truelove

106
"Перестаньте робити if (!$scope.$$phase) $scope.$apply()", github.com/angular/angular.js/wiki/Anti-Patterns
anddoutoi

34
@anddoutoi: Погоджено; ви посилаєтесь, це чітко зрозуміло, що це не рішення; однак я не впевнений, що означає "ти недостатньо високий у стеку викликів". Ви знаєте, що це означає?
Тревор

13
@threed: див. відповідь aaronfrost. Правильний спосіб - використовувати відстрочку для запуску дайджесту в наступному циклі. Інакше подія загубиться і зовсім не оновить область.
Марек

663

З недавньої дискусії з кутовими хлопцями на цю саму тему: З причин, що підтверджують майбутнє, не слід використовувати$$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, тому для мене це досить добре!


5
Я багато разів стикався з цим у своїх директивах. Писав одного для редактора, і це виявилося чудово. Я був на зустрічі з Бредом Гріном, і він сказав, що Angular 2.0 буде величезним, без циклу дайджесту, використовуючи вбудовану здатність спостерігати JS та використовувати поліфункцію для браузерів, яких цього не вистачає. У цей момент нам більше не потрібно буде цього робити. :)
Майкл Дж. Калкінс

Вчора я бачив проблему, коли виклик selectize.refreshItems () всередині $ timeout викликав жахливу рекурсивну помилку дайвісту . Будь-які ідеї, як це могло бути?
iwein

3
Якщо ви використовуєте, $timeoutа не рідне setTimeout, чому ви не використовуєте $windowзамість рідного window?
LeeGee

2
@LeeGee: Сенс використання $timeoutв цьому випадку полягає в тому, що $timeoutзабезпечує правильне оновлення кутової області. Якщо дайджест $ не виконується, це призведе до запуску нового дайджесту $.
трепет

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 були нашими улюбленими досі. Ці методи успішно кажуть угловому очікувати до наступного циклу анімації, що гарантуватиме, що поточна область застосування.


2
Чи це порівнянно з використанням $ timeout (...)? Я використовував $ timeout у кількох випадках, щоб перейти до наступного циклу подій, і, здається, він працює добре - хтось знає, чи є причина не використовувати $ timeout?
Тревор

9
Це дійсно слід використовувати лише тоді, коли ви вже використовуєте underscore.js. Це рішення не варто імпортувати всю бібліотеку підкреслення лише для використання її deferфункції. Я дуже віддаю перевагу цьому $timeoutрішенню, тому що всі вже мають доступ $timeoutчерез кутовий, без будь-яких залежностей від інших бібліотек.
тенісист

10
Правда ... але якщо ви не використовуєте підкреслення або подачу ... вам потрібно переоцінити, що ви робите. Ці два вкладки змінили те, як виглядає код.
морозний

2
Ми вважаємо квартиру залежністю від Restangular (незабаром ми будемо ліквідувати Restangular на користь ng-route). Я думаю, що це хороша відповідь, але це не надто припустити, що люди хочуть використовувати підкреслення / подачу. Всіх кошти цього ЛІЕС відмінно ... якщо ви використовуєте їх досить ... в ці дні я використовую методи , які ES5 знищити 98% чому - то використовується , щоб включити підкреслення.
BradGreens

2
Ви праві @SgtPooki. Я змінив відповідь, щоб включити також можливість використання $ timeout. $ timeout та _.defer будуть дочекатися наступного циклу анімації, що забезпечить закінчення поточної області застосування. Дякуємо, що тримаєте мене чесно і змушуєте мене тут оновити відповідь.
морозний

267

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

Речі, які ви повинні знати

  • $$phase є приватною основою, і для цього є вагомі причини.

  • $timeout(callback)буде чекати, поки завершиться поточний цикл дайвінгу (якщо такий є), потім виконати зворотний виклик, а потім виконати в кінці повний $apply.

  • $timeout(callback, delay, false)зробить те ж саме (з необов'язковою затримкою перед виконанням зворотного дзвінка), але не запустить $apply(третій аргумент), який зберігає продуктивність, якщо ви не змінили модель Angular ($ range).

  • $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 замість $ застосовувати для збереження виступу.

Оновлення з моменту Angularjs 1.2

Новий, потужний метод був доданий в будь-який $ обсязі: $evalAsync. В основному, він виконуватиме зворотний виклик у поточному циклі дайджесту, якщо такий відбувається, інакше новий цикл дайвінгу почне виконувати зворотний виклик.

Це все ще не так добре, як $scope.$digestякщо ви дійсно знаєте, що вам потрібно синхронізувати лише ізольовану частину HTML-коду (оскільки $applyбуде запущено нове , якщо жодне не виконується), але це найкраще рішення при виконанні функції який ви не можете знати, чи буде він виконаний синхронно чи ні , наприклад, після отримання ресурсу, який потенційно кешований: іноді це вимагатиме виклику асинхронізації на сервер, інакше ресурс буде локально отриманий синхронно.

У цих випадках та інших, де у вас було !$scope.$$phase, обов'язково використовуйте$scope.$evalAsync( callback )


4
$timeoutкритикується попутно. Чи можете ви навести більше причин, щоб цього уникати $timeout?
mlhDev

88

Зручний маленький помічник, щоб зберегти цей процес ДУХИМ:

function safeApply(scope, fn) {
    (scope.$$phase || scope.$root.$$phase) ? fn() : scope.$apply(fn);
}

6
Ваше SafeApply допомогло мені зрозуміти, що відбувається набагато більше, ніж усе інше. Дякуємо, що опублікували це.
Джейсон Ще

4
Я збирався зробити те саме, але чи це не означає, що є ймовірність, що зміни, внесені нами в fn (), не будуть помічені $ дайджестом? Чи не було б краще затримати функцію, припускаючи область застосування. $$ 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();

  }]
)

32

Див. Http://docs.angularjs.org/error/$rootScope:inprog

Проблема виникає, коли у вас є виклик, $applyякий іноді запускається асинхронно поза кодовим кодом (коли слід застосовувати $ apply), а іноді синхронно всередині кутового коду (що викликає $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тоді, коли ви знаєте, який код працює, він завжди буде виконуватись поза кодовим кодом (наприклад, ваш дзвінок до $ застосовуватиметься всередині зворотного дзвінка, який викликається кодом поза вашим кодовим кодом).

Якщо хтось не знає про якийсь негативний недолік для використання $timeoutнад $apply, я не бачу, чому ви не завжди могли використовувати $timeout(з нульовою затримкою) замість цього $apply, оскільки це зробить приблизно те саме.


Дякую, це працювало для мого випадку, коли я не дзвоню, $applyале все ж отримую помилку.
ariscris

5
Основна відмінність полягає в тому, що $applyце синхронно (його зворотний виклик виконується, тоді код після $ застосовується), поки $timeoutце не так: виконується поточний код після таймауту, після чого новий стек починається з зворотного дзвінка, як якщо б ви використовували setTimeout. Це може призвести до графічних збоїв, якщо ви оновлювали двічі одну і ту ж модель: $timeoutбудете чекати, коли перегляд оновиться, перш ніж оновити його.
floribon

Дякую дійсно, кинув. У мене був метод, викликаний у результаті деякої $ watch активності, і я намагався оновити інтерфейс користувача до того, як мій зовнішній фільтр закінчив виконувати. Якщо встановити, що всередині функції $ timeout працювало на мене.
djmarquette

28

Коли ви отримуєте цю помилку, це в основному означає, що вона вже знаходиться в процесі оновлення вашого перегляду. Вам дійсно не потрібно телефонувати $apply()в контролер. Якщо ваш погляд не оновлюється так, як ви очікували, і ви отримуєте цю помилку після виклику $apply(), це, швидше за все, означає, що ви не правильно оновили модель. Якщо ви опублікуєте якусь специфіку, ми зможемо з'ясувати основну проблему.


хе, я витратив цілий день, щоб дізнатися, що AngularJS просто не може дивитись прив’язки "магічно", і я мушу іноді штовхати його за допомогою $ apply ().
OZ_

що взагалі означає you're not updating the the model correctly? $scope.err_message = 'err message';не правильне оновлення?
OZ_

2
Єдиний час, коли вам потрібно зателефонувати, $apply()- це коли ви оновлюєте модель «поза» кутового (наприклад, з плагіна jQuery). Легко потрапити в пастку вигляду, не виглядаючи правильно, і тому ви кидаєте купу $apply()s скрізь, що потім закінчується помилкою, поміченою в ОП. Коли я кажу, you're not updating the the model correctlyя просто маю на увазі всю логіку бізнесу, неправильно заповнюючи щось, що може бути в межах сфери, що призводить до того, що погляд не виглядає так, як очікувалося.
dnc253

@ dnc253 Я згоден, і відповідь написав. Знаючи те, що я зараз знаю, я б використав $ timeout (function () {...}); Це робить те саме, що робить _.defer. Вони обидва переходять до наступного циклу анімації.
морозний


11

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

scope.evalAsync(function(scope){
    //use the scope...
});

10

Перш за все, не фіксуйте це таким чином

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

Це не має сенсу, оскільки $ фаза - це лише булевий прапор для циклу дайджестів $, тому ваш $ apply () іноді не запускається. І пам’ятайте, що це погана практика.

Натомість використовуйте $timeout

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

Якщо ви використовуєте підкреслення або позначення, ви можете використовувати defer ():

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

9

Іноді ви все одно отримаєте помилки, якщо скористаєтесь цим способом ( https://stackoverflow.com/a/12859093/801426 ).

Спробуйте це:

if(! $rootScope.$root.$$phase) {
...

5
використовуючи обидві! $ область. $$ фаза і! $ область. $ root. $$ фаза (не! $ rootScope. $ root. $$ фаза) працює для мене. +1
апронт

2
$rootScopeі anyScope.$rootтой самий хлопець. $rootScope.$rootє зайвим.
floribon


5

спробуйте використовувати

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

замість

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

$ applyAsync Графік виклику $ застосовуватиметься, щоб це відбулося пізніше. Це можна використовувати для встановлення черги на кілька виразів, які потрібно оцінити в одному дайджесті.

ПРИМІТКА. У межах дайджесту $ $ applicationAsync () буде промиватися лише у тому випадку, якщо поточна область є $ rootScope. Це означає, що якщо ви зателефонуєте $ digest у дочірній області, вона не буде неявно змивати чергу $ applyAsync ().

Приклад:

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

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

Список літератури:

1. Обсяг. $ ApplyAsync () проти Scope. $ EvalAsync () в AngularJS 1.3

  1. Документи AngularJs

4

Я б радив вам скористатися спеціальною подією, а не викликати дайджест циклу.

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

Створюючи власну подію, ви також ефективніше використовуєте свій код, оскільки ви запускаєте лише слухачів, підписаних на цю подію, а НЕ запускаєте всі годинники, прив’язані до області, як це було б, якщо ви посилаєтесь на область застосування.

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


$scope.$broadcast('customEventName', optionalCustomEventArguments);

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);

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це все, що вам потрібно зробити.



1

Розуміння того, що кутові документи виклику перевірки $$phaseв антішаблон , я намагався отримати $timeoutі _.deferпрацювати.

Методи очікування та відкладеного часу створюють спалах нерозбірливого {{myVar}}вмісту в домі, як ПІДБУТ . Для мене це було неприйнятно. Це не дозволяє мені догматично сказати, що щось є злом, і не маю підходящої альтернативи.

Єдине, що працює щоразу:

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

Я не розумію небезпеки цього методу, або чому він описується як злом людей у ​​коментарях та кутовій команді. Команда здається точною та легкою для читання:

"Зробіть дайджест, якщо вже не відбувається"

У CoffeeScript це ще красивіше:

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

У чому проблема з цим? Чи існує альтернатива, яка не створить FOUT? $ safeApply виглядає чудово, але також використовується $$phaseметод перевірки.


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']);
};

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

Коли я відключив налагоджувач, помилка більше не відбувається. У моєму випадку це було через те, що відладчик зупиняв виконання коду.


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

Проблема в основному виникає тоді, коли ми просимо кутовий запустити дайджест циклу, навіть незважаючи на те, що його процес перетворює проблему на точку розуміння. наслідок виключення в консолі.
1. Не має сенсу викликати область. $ Apply () всередині функції $ timeout, оскільки внутрішньо він робить те саме.
2. Код поширюється на функцію JavaScript ванілі, оскільки його власний не визначений кутовий кут, тобто setTimeout
3. Для цього можна скористатися

if (!
Obseg . $$ фаза) { obseg. $ EvalAsync (function () {

}); }


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

Ось хороше рішення, щоб уникнути цієї помилки та уникати $ apply

Ви можете комбінувати це з debounce (0), якщо дзвонити на основі зовнішньої події. Вище наведено «дебютацію», яку ми використовуємо, та повний приклад коду

.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;
            };
        };
    }
])

і сам код, щоб прослухати якусь подію та зателефонувати в дайджест $ лише в потрібному вам обсязі $

        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 для створення функцій "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
}

Не робіть, якщо (! $ Obseg. $$ фаза) $ область. $ Застосовується (), це означає, що ваш обсяг $.
MGot90
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.