AngularJS: $ scope çağrılırken devam eden $ digest hatasını önle.


838

Açısal bir uygulama oluşturduğumdan beri sayfamı kapsamıma manuel olarak güncellemem gerektiğini fark ediyorum.

Bunu yapmanın tek yolu $apply()denetleyicilerimin ve yönergelerimin kapsamından çağırmak . Buradaki sorun, aşağıdakileri okuyan konsola bir hata atmaya devam etmesidir:

Hata: $ digest zaten devam ediyor

Herkes bu hatayı önlemek veya aynı şeyi ancak farklı bir şekilde elde biliyor mu?


34
Gerçekten daha sinir bozucu bir şey kullanmak $ $ kullanmak gerekir.
OZ_

Ben bile geri çağrı $ uygulamak çağırıyorum bu hatayı da alıyorum. Sunuculardaki verilere erişmek için bir üçüncü taraf kitaplığı kullanıyorum, bu yüzden $ http'den yararlanamıyorum, çünkü ben de http http kullanmak için kitaplıklarını yeniden yazmak zorunda kalacağım.
Trevor

45
kullanım$timeout()
Onur Yıldırım

6
$ timeout (fn) + 1 kullanın, Sorunu düzeltebilir,! $ kapsam. $$ aşaması en iyi çözüm değildir.
Huei Tan

1
Sadece kodu / çağrı kapsamını sarın. $ Dan uygulamak içinde zaman aşımı (değil $ zaman aşımı) AJAX fonksiyonları (değil $ http) ve olaylar (değil ng-*). Bir işlev içinden (zaman aşımı / ajax / events ile çağrılır) çağırıyorsanız, başlangıçta yükte de çalıştırılmadığından emin olun.
Patrick

Yanıtlar:


660

Bu kalıbı kullanmayın - Bu, çözdüğünden daha fazla hataya neden olur. Bir şeyi düzelttiğini düşünseniz bile, olmadı.

A'nın devam edip etmediğini $digestkontrol ederek kontrol edebilirsiniz $scope.$$phase.

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

$scope.$$phasedönecek "$digest"veya "$apply"a $digestveya $applydevam ediyorsa . Bu devletler arasındaki farkın $digest, mevcut kapsamın saatlerini ve çocuklarını $applyişleyeceği ve tüm kapsamların izleyicilerini işleyeceğine inanıyorum.

@ Dnc253'e gelince, kendinizi sık sık aradığınızı $digestya da $applysık sık bulduğunuzda yanlış yapıyor olabilirsiniz. Genelde Angular'ın ulaşamayacağı bir DOM etkinliğinin bir sonucu olarak kapsamın durumunu güncellemem gerektiğinde sindirmem gerektiğine inanıyorum. Örneğin, bir twitter bootstrap modal gizlendiğinde. Bazen DOM olayı, a işlemi $digestdevam ederken tetiklenir, bazen de gerçekleşmez. Bu yüzden bu kontrolü kullanıyorum.

Biri bilirse daha iyi bir yol bilmek isterim.


Yorumlardan: @anddoutoi tarafından

angular.js Desen Karşıtı

  1. Yapmayın if (!$scope.$$phase) $scope.$apply(), bu $scope.$apply()çağrı yığınında yeterince yüksek olmadığı anlamına gelir .

230
Görünüşe göre $ digest / $ Apply bunu varsayılan olarak yapmalı
Roy Truelove

21
Bazı durumlarda geçerli kapsamı VE kök kapsamını kontrol etmem gerektiğini unutmayın. Kökte $$ aşaması için bir değer alıyorum ama kapsamımda değil. Bir direktifin yalıtılmış kapsamıyla ilgili bir şey olduğunu düşünün, ama ..
Roy Truelove

106
" Yapmayı bırak if (!$scope.$$phase) $scope.$apply()", github.com/angular/angular.js/wiki/Anti-Patterns
anddoutoi

34
@anddoutoi: Kabul edildi; bağlantınız, bunun çözüm olmadığını oldukça netleştirir; Ancak, "çağrı yığınında yeterince yüksek değilsiniz" ile ne kastedildiğinden emin değilim. Bunun ne anlama geldiğini biliyor musun?
Trevor

13
@ threed: aaronfrost'un cevabına bakınız. Doğru yol, sonraki döngüde özeti tetiklemek için ertelemeyi kullanmaktır. Aksi takdirde etkinlik kaybolacak ve kapsamı hiç güncellemeyecektir.
Marek

663

Açısal adamlarla bu konuyla ilgili son bir tartışmadan: Gelecekteki prova nedenlerinden dolayı,$$phase

Bunu yapmak için "doğru" yol için basıldığında, cevap şu anda

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

Son zamanlarda, değişen derecelerde geri aramaların verildiği facebook, google ve twitter API'lerini sarmak için açısal hizmetler yazarken buna girdim.

Bir hizmetin içinden bir örnek. (Kısacası, hizmetin geri kalanı - değişkenleri ayarlayan, enjekte edilen $ zaman aşımı vb. - bırakıldı.)

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 için gecikme bağımsız değişkeninin isteğe bağlı olduğunu ve ayarlanmadan bırakıldığında varsayılan olarak 0 olacağını unutmayın ( $ timeout , gecikme ayarlanmamışsa varsayılan olarak 0 olan $ browser.defer öğesini çağırır )

Biraz sezgisel değil, ama Angular yazan adamların cevabı, bu yüzden benim için yeterince iyi!


5
Direktiflerimde birçok kez bununla karşılaştım. Redatör için bir tane yazıyordum ve bu mükemmel bir şekilde işe yaradı. Brad Green ile bir buluşmadaydım ve Angular 2.0'ın JS'nin yerel gözlem yeteneğini kullanarak ve buna sahip olmayan tarayıcılar için bir çoklu dolgu kullanarak sindirim döngüsü olmadan çok büyük olacağını söyledi. Bu noktada artık bunu yapmamız gerekmeyecek. :)
Michael J.Calkins

Dün $ timeout içinde selectize.refreshItems () öğesini çağırmanın korkunç özyinelemeli özüm hatasına neden olduğu bir sorun gördüm . Bunun nasıl olabileceğine dair bir fikrin var mı?
Mart'ta iwein

3
$timeoutYerel yerine kullanmak yerine setTimeout, neden yerel $windowyerine kullanmıyorsunuz window?
LeeGee

2
@LeeGee: $timeoutBu durumda kullanım noktası $timeout, açısal kapsamın düzgün şekilde güncellenmesini sağlamaktır. Bir $ özeti devam etmiyorsa, yeni bir $ özeti çalışmasına neden olur.
huşu

2
@webicy Bu bir şey değil. $ Timeout'a iletilen işlevin gövdesi çalıştırıldığında, vaat zaten çözülmüştür! Kesinlikle hiçbir nedeni yok cancel. Gönderen docs : "Bunun bir sonucu olarak, söz bir reddi ile çözülecektir." Çözülmüş bir vaadi çözemezsiniz. İptal işleminiz herhangi bir hataya neden olmaz, ancak olumlu bir şey de yapmaz.
daemonexmachina

324

Özet döngüsü eşzamanlı bir çağrıdır. Tamamlanana kadar tarayıcının olay döngüsüne denetim sağlamaz. Bununla başa çıkmanın birkaç yolu vardır. Bununla başa çıkmanın en kolay yolu yerleşik $ zaman aşımını kullanmaktır ve ikinci bir yol alt çizgi veya lodash kullanıyorsanız (ve olmalısınız), aşağıdakileri arayın:

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

veya lodash'ınız varsa:

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

Birkaç geçici çözüm denedik ve tüm kontrolörlerimize, yönergelerimize ve hatta bazı fabrikalara $ rootScope enjekte etmekten nefret ettik. Yani, $ timeout ve _.defer şimdiye kadar bizim favori oldu. Bu yöntemler, açısal olarak, geçerli kapsamın geçerli olduğunu garanti eden bir sonraki animasyon döngüsüne kadar beklemesini söyler.


2
Bu, $ timeout (...) ile karşılaştırılabilir mi? Bir sonraki olay döngüsüne ertelemek için birkaç durumda $ timeout kullandım ve iyi çalışıyor gibi görünüyor - kimse $ timeout kullanmamanın bir nedeni olup olmadığını biliyor mu?
Trevor

9
Bu gerçekten sadece zaten kullanıyorsanız kullanılmalıdır underscore.js. Bu çözüm, yalnızca deferişlevini kullanmak için alt çizgi kitaplığının tamamını içe aktarmaya değmez . $timeoutÇözümü çok tercih ediyorum, çünkü herkes zaten $timeoutdiğer kütüphanelere bağımlı olmadan açısal aracılığıyla erişebiliyor .
Tennisgent

10
Doğru ... ama alt çizgi veya lodash kullanmıyorsanız ... ne yaptığınızı yeniden değerlendirmeniz gerekir. Bu iki libs kodun görünüşünü değiştirdi.
ayaz

2
Restangular'a bağımlılık olarak lodash'ımız var (Restangular'ı ng-rotası lehine ortadan kaldıracağız). Bence bu iyi bir cevap ama insanların alt çizgi / lodash kullanmak istediklerini varsaymak harika değil. Elbette bu kütüphaneler iyi ... eğer onları yeterince kullanırsanız ... bu günlerde alt çizgi eklemek için kullandığım nedenin% 98'ini silen ES5 yöntemlerini kullanıyorum .
BradGreens

2
Haklısın @SgtPooki. Cevabı, $ timeout kullanma seçeneğini de içerecek şekilde değiştirdim. $ timeout ve _.defer, bir sonraki animasyon döngüsüne kadar bekleyecek ve mevcut kapsamın. Beni dürüst tuttuğun ve yanıtı burada güncellememi sağladığın için teşekkürler.
ayaz

267

Buradaki cevapların çoğu iyi tavsiyeler içerir, ancak karışıklığa da yol açabilir. Basitçe kullanarak $timeoutbir değil en iyi ne de doğru çözüm. Ayrıca, performanslar veya ölçeklenebilirlik konusunda endişeleriniz varsa bunu mutlaka okuyun.

Bilmeniz gerekenler

  • $$phase çerçeveye özeldir ve bunun için iyi sebepler vardır.

  • $timeout(callback)mevcut özet döngüsü (eğer varsa) tamamlanana kadar bekleyecek, sonra geri aramayı yürütebilecek ve sonunda bir tam çalışacaktır $apply.

  • $timeout(callback, delay, false)aynısını yapar (geri aramayı yürütmeden önce isteğe bağlı bir gecikmeyle), ancak $applyAçısal modelinizi ($ kapsam) değiştirmediyseniz performans tasarrufu sağlayan bir (üçüncü argüman) tetiklemez.

  • $scope.$apply(callback)diğer şeylerin yanı sıra, $rootScope.$digestyalıtılmış bir kapsam dahilinde olsanız bile uygulamanın ve tüm çocuklarının kök kapsamını en aza indireceği anlamına gelir.

  • $scope.$digest()modelini görünümle senkronize eder, ancak ebeveyninizin kapsamını sindirmez, bu da HTML'nizin yalıtılmış bir kısmı üzerinde yalıtılmış bir kapsamla çalışırken (çoğunlukla bir yönergeden) çok fazla performans kaydedebilir. $ digest bir geri arama almaz: kodu çalıştırırsınız, sonra sindirirsiniz.

  • $scope.$evalAsync(callback)angularjs 1.2 ile tanıtıldı ve muhtemelen sorunlarınızın çoğunu çözecek. Bu konuda daha fazla bilgi edinmek için lütfen son paragrafa bakın.

  • alırsanız $digest already in progress error, mimariniz yanlıştır: ya kapsamınızı en aza indirmenize gerek yoktur ya da bundan sorumlu olmamalısınız (aşağıya bakın).

Kodunuzu yapılandırma

Bu hatayı aldığınızda, kapsamınızı zaten devam ederken sindirmeye çalışıyorsunuz: o noktada kapsamınızın durumunu bilmediğiniz için, sindirimiyle uğraşmaktan sorumlu değilsiniz.

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

Ve büyük bir Açısal uygulamanın bir parçasıyken yalıtılmış bir küçük yönerge üzerinde ne yaptığınızı ve çalıştığınızı biliyorsanız, performansları kaydetmek için $ uygulama yerine $ digest'i tercih edebilirsiniz.

Angularjs 1.2'den beri güncelleme

Yeni, güçlü yöntem herhangi $ kapsamına eklendi: $evalAsync. Temel olarak, eğer varsa, geri çağırma geçerli sindirim döngüsü içinde yürütülür, aksi takdirde yeni bir sindirim döngüsü geri çağrıyı yürütmeye başlar.

Bu, $scope.$digestHTML'nizin yalnızca yalıtılmış bir bölümünü senkronize etmeniz gerektiğini gerçekten biliyorsanız iyi değildir (çünkü $applyhiçbiri devam etmiyorsa yeni bir tetiklenir), ancak bir işlevi yürütürken bu en iyi çözümdür hangi eşzamanlı ya da değil çalıştırılır eğer bunu bilemez bazen bu bir sunucuya bir zaman uyumsuz çağrı gerektirecektir aksi kaynak eşzamanlı lokal getirilen olacak: potansiyel olarak önbelleğe bir kaynak getirilirken sonra örneğin.

Bu durumlarda ve sahip olduğunuz diğer tüm durumlarda !$scope.$$phase,$scope.$evalAsync( callback )


4
$timeoutgeçerken eleştirilir. Sakınmak için daha fazla neden verebilir misiniz $timeout?
mlhDev

88

Bu işlemi KURU tutmak için kullanışlı küçük bir yardımcı yöntem:

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

6
SafeApply, neler olup bittiğini her şeyden çok daha fazla anlamama yardımcı oldu. Bunu gönderdiğiniz için teşekkürler.
Jason More

4
Aynı şeyi yapmak üzereydim, ama bunu yapmak fn () 'de yaptığımız değişikliklerin $ digest tarafından görülmeyeceği anlamına gelmiyor mu? Kapsamı varsayarak işlevi geciktirmek daha iyi olmaz mıydı. $$ phase === '$ digest'?
Spencer Alger

Kabul ediyorum, bazen $ uygula () özeti tetiklemek için kullanılır, sadece fn'i tek başına çağırır ... bu bir sorunla sonuçlanmaz mı?
CMCDragonkai

1
scope.$apply(fn);Olması gerektiği gibi hissediyorum scope.$apply(fn());çünkü fn () fn değil, işlevi yürütecek. Lütfen yanıldığım yerde bana yardım et
madhu131313

1
@ZenOut $ Apply çağrısı, işlevler dahil olmak üzere birçok farklı türde argümanı destekler. Bir işlev iletilirse, işlevi değerlendirir.
boxmein

33

CodeMirror ve Krpano gibi üçüncü taraf komut dosyalarında da aynı sorunu yaşadım ve hatta burada belirtilen safeApply yöntemlerini kullanmak benim için hatayı çözmedi.

Ama ne çözdü $ timeout hizmeti kullanıyor (önce enjekte etmeyi unutmayın).

Böylece, şöyle bir şey:

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

ve eğer kodunuzun içinde kullanıyorsanız

bu

belki de bir fabrika direktifinin kontrolörünün içinde olduğu veya sadece bir çeşit bağlamaya ihtiyaç duyduğu için, şöyle bir şey yapardınız:

.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

Bkz. Http://docs.angularjs.org/error/$rootScope:inprog

Bu $apply, bazen Açısal kodun dışında zaman uyumsuz olarak çalıştırıldığında ($ uygulamanız kullanıldığında) ve bazen de Açısal kod içinde ( $digest already in progresshataya neden olan ) zaman uyumlu olarak çalıştırıldığında bir sorun olduğunda ortaya çıkar .

Örneğin, bir sunucudan öğeleri eşzamansız olarak getiren ve önbelleğe alan bir kitaplığınız olduğunda bu olabilir. Bir öğe ilk kez istendiğinde, kod yürütülmesini engellememek için eşzamansız olarak alınır. Ancak ikinci kez, öğe eşzamanlı olarak alınabilmesi için zaten önbellektir.

Bu hatayı önlemenin yolu, çağıran kodun $applyeşzamansız olarak çalışmasını sağlamaktır . Bu, kodunuzu bir $timeoutgecikme 0(varsayılan) olarak ayarlanmış bir çağrı içinde çalıştırarak yapılabilir . Ancak, kodunuzu içeride çağırmak arama $timeoutzorunluluğunu ortadan kaldırır $apply, çünkü $ timeout $digestkendi başına başka bir döngüyü tetikler , bu da gerekli tüm güncellemeleri vb.

Çözüm

Kısacası, bunu yapmak yerine:

... your controller code...

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

... more of your controller code...

Bunu yap:

... your controller code...

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

... more of your controller code...

Yalnızca $applykodu çalıştıran kodun her zaman Açısal kodun dışında çalıştırılacağını bildiğinizde arayın (ör. $ Apply çağrınız, Açısal kodunuzun dışındaki kod tarafından çağrılan bir geri arama içinde gerçekleşir).

Birisi kullanmanın bazı etkili dezavantaj farkında olmadığı sürece $timeoutüzerinde $applyher zaman kullanamadı neden görmüyorum $timeoutyerine (sıfır gecikme ile) $applyyaklaşık aynı şeyi yapacak gibi.


Teşekkürler, bu benim aradığım durumum için çalıştı $apply kendime değil hala hatayı alıyorum .
ariscris

5
Temel fark, $applysenkronize olmasıdır (geri çağrısı yürütülür, ardından $ uygula'yı izleyen kod) $timeoutdeğil: zaman aşımını izleyen geçerli kod yürütülür, sonra yeni bir yığın, sanki kullanıyorsunuz gibi geri çağrısıyla başlar setTimeout. Aynı modeli iki kez güncelliyorsanız grafik aksaklıklarına yol açabilir: $timeouttekrar güncellemeden önce görünümün yenilenmesini bekleyecektir.
floribon

Gerçekten teţekkürler. Bazı $ watch etkinliği sonucu olarak adlandırılan bir yöntem vardı ve benim dış filtre yürütme bitmeden UI güncellemeye çalışıyordu. Bunu $ timeout fonksiyonunun içine koymak benim için çalıştı.
djmarquette

28

Bu hatayı aldığınızda, temel olarak görünümünüzü güncelleme sürecinde olduğu anlamına gelir. Gerçekten $apply()kumandanızda aramanıza gerek yok . Görünümünüz beklediğiniz gibi güncellenmiyorsa ve aramadan sonra bu hatayı alırsanız $apply(), büyük olasılıkla modeli doğru bir şekilde güncellemediğiniz anlamına gelir. Bazı özellikleri gönderirseniz, temel sorunu çözebiliriz.


heh, bütün gün geçirdim AngularJS sadece "sihirli" bağları izleyemez ve bazen $ Apply () ile onu itmek gerekir.
OZ_

ne anlama geliyor you're not updating the the model correctly? $scope.err_message = 'err message';doğru güncelleme değil mi?
OZ_

2
Aramanız gereken tek zaman $apply()açısal "dış" modelini güncellemenizdir (örn. Bir jQuery eklentisinden). Doğru görünmeyen tuzağın içine düşmek kolaydır ve böylece $apply()her yerde bir sürü s atarsınız , bu da OP'de görülen hatayla sonuçlanır. Dediğim zaman, you're not updating the the model correctlytüm iş mantığının kapsamda olabilecek herhangi bir şeyi doğru şekilde doldurmadığını kastediyorum, bu da görünümün beklendiği gibi görünmemesine neden oluyor.
dnc253

@ dnc253 Kabul ediyorum ve cevabı yazdım. Şimdi ne bildiğimi bilerek, $ timeout (function () {...}); _.Defer ile aynı şeyi yapar. Her ikisi de bir sonraki animasyon döngüsüne erteler.
ayaz

14

En kısa kasa $applyşekli:

$timeout(angular.noop)

11

EvalAsync'i de kullanabilirsiniz. Sindirim bittikten bir süre sonra çalışacaktır!

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

10

Her şeyden önce, bu şekilde düzeltme

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

Bu mantıklı değil çünkü $ fazı $ digest döngüsü için yalnızca bir boole bayrağı olduğundan, $ Apply () bazen çalışmayacaktır. Ve bunun kötü bir uygulama olduğunu unutmayın.

Bunun yerine, $timeout

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

Alt çizgi veya lodash kullanıyorsanız, defer () yöntemini kullanabilirsiniz:

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

9

Bazen bu şekilde kullanırsanız hatalar alabilirsiniz ( https://stackoverflow.com/a/12859093/801426 ).

Bunu dene:

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

5
! $ scope. $$ phase ve! ​​$ scope. $ root. $$ phase (değil! $ rootScope. $ root. $$ phase) benim için çalışıyor. +1
asprotte

2
$rootScopeve anyScope.$rootaynı adam. $rootScope.$rootgereksizdir.
floribon


5

kullanmayı dene

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

onun yerine

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

$ ApplyAsync $ Apply çağrısını daha sonra gerçekleşecek şekilde zamanlayın. Bu, aynı özet içinde değerlendirilmesi gereken birden fazla ifadeyi sıralamak için kullanılabilir.

NOT: $ digest içinde, $ applicationAsync () yalnızca geçerli kapsam $ rootScope ise temizlenir. Bu, bir alt kapsamda $ digest çağırırsanız, örtülü olarak $ applicAsync () kuyruğunu temizlemeyeceği anlamına gelir.

xmaple:

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

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

Referanslar:

1. Scope. $ ApplyAsync () ve AngularJS 1.3'te Scope. $ EvalAsync ()

  1. AngularJs Dokümanları

4

Bir özet döngüsünü tetiklemek yerine özel bir etkinlik kullanmanızı öneririm.

Özel etkinlikler yayınlamanın ve dinleyicileri bu etkinlikler için kaydetmenin, bir özet programında olsanız da olmasanız da gerçekleşmesini istediğiniz bir eylemi tetiklemek için iyi bir çözüm olduğunu buldum.

Özel bir etkinlik oluşturarak kodunuzla daha verimli olursunuz çünkü yalnızca söz konusu etkinliğe abone olan dinleyicileri tetiklersiniz ve kapsamı çağırdıysanız kapsama bağlı tüm saatleri tetiklemezsiniz.

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


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

3

yearofmoo bizim için yeniden kullanılabilir bir güvenli uygulama oluşturmada iyi bir iş çıkardı:

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

Kullanımı:

//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

İşlevin çalışacağını bildiğim yerler $evalyerine arayarak bu sorunu çözebildim .$apply$digest

Dokümanlara göre , $applytemelde bunu yapar:

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

Benim durumumda, bir ng-clickkapsamdaki bir değişkeni değiştirir ve bu değişken üzerindeki bir $ watch, olması gereken diğer değişkenleri değiştirir $applied. Bu son adım, "özüm zaten devam ediyor" hatasına neden olur.

Değiştirerek $applyile $evalizle ifadenin içinde kapsamı değişkenler olarak beklenen güncelleme olsun.

Nedenle, göründüğü özeti çünkü açısal içinde diğer bazı değişimin her durumda çalışan olacak, reklamın söz konusu $eval'ing yapmanız gereken tek şey.



1

Açısal belgelerin $$phasebir anti-desen kontrol çağırdığını anlayarak, çalışmaya $timeoutve _.deferçalışmaya çalıştım .

Zaman aşımı ve ertelenmiş yöntemler {{myVar}}, dom'ta bir FOUT gibi ayrıştırılmamış içeriğin bir flaşını oluşturur . Benim için bu kabul edilemezdi. Bana dogmatik olarak bir şeyin bir saldırı olduğunu ve uygun bir alternatifi olmadığı söylenecek çok şey bırakmıyor.

Her seferinde işe yarayan tek şey:

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

Bu yöntemin tehlikesini veya yorumlarda ve açısal ekipte neden insanlar tarafından bir saldırı olarak tanımlandığını anlamıyorum. Komut kesin ve okunması kolay görünüyor:

"Şimdiye kadar bir şey olmadıkça özüm yapın"

CoffeeScript'te daha da güzel:

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

Bununla ilgili sorun nedir? Bir FOUT yaratmayacak bir alternatif var mı? $ safeApply iyi görünüyor ancak $$phaseinceleme yöntemini de kullanıyor.


1
Bu soruya bilinçli bir yanıt görmek isterim!
Ben Wheeler

Bu bir hack çünkü bağlamı kaçırdığınız veya kodu bu noktada anlamadığınız anlamına gelir: ya açısal sindirim döngüsü içindesiniz ve buna ihtiyacınız yok ya da eşzamansız olarak bunun dışındasınız ve sonra buna ihtiyacınız var. Kodun bu noktasında bunu bilemezseniz, o zaman sindirmekten sorumlu değilsiniz
floribon

1

Bu benim utils hizmet:

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

ve bu kullanımı için bir örnek:

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

Bu yöntemi kullanıyorum ve mükemmel çalışıyor gibi görünüyor. Bu, çevrimin bittiği zamanı bekler ve sonra tetiklenir apply(). apply(<your scope>)İstediğiniz yerden işlevi çağırmanız yeterlidir .

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

Hata ayıklayıcıyı devre dışı bıraktığımda, hata artık gerçekleşmiyor. Benim durumumda , hata ayıklayıcı kod yürütmeyi durdurma nedeniyle oldu.


0

Yukarıdaki yanıtlara benzer ancak bu benim için sadakatle çalıştı ... bir hizmette ekle:

    //sometimes you need to refresh scope, use this to prevent conflict
    this.applyAsNeeded = function (scope) {
        if (!scope.$$phase) {
            scope.$apply();
        }
    };

0

Kullanabilirsiniz

$timeout

hatayı önlemek için.

 $timeout(function () {
                        var scope = angular.element($("#myController")).scope();
                        scope.myMethod();
                        scope.$scope();
                    },1);

$ Timeout kullanmak istemezsem
rahim.nagori

0

Sorun, temelde, kavramaya açısal bir sorun yaratan süreçte olsa da, sindirim döngüsünü çalıştırmak için açısal olmasını talep ettiğimizde ortaya çıkıyor. konsolda sonuç istisnası.
1. Kapsamı çağırmanın bir anlamı yoktur. $ Timeout işlevi içinde $ Apply (), dahili olarak aynısını yapar.
2. Kod vanilya JavaScript işleviyle uyumludur çünkü doğal açısal açısal tanımlanmamış yani setTimeout
3. Bunu yapmak için

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

}); }


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

Bu hatayı önlemek ve $ uygulamaktan kaçınmak için iyi bir çözüm

harici etkinliğe göre arama yapıyorsanız bunu debounce (0) ile birleştirebilirsiniz. Yukarıda kullandığımız 'ayrılma' ve kodun tam örneği

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

ve bazı olayları dinlemek için kodun kendisi ve $ digest'i yalnızca ihtiyacınız olan $ kapsamında çağırmak

        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

Bunu buldum : https://coderwall.com/p/ngisma burada Nathan Walker (sayfanın altına yakın), $ rootScope'ta bir dekoratör önererek 'safeApply', kodu oluşturun:

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

Bu sorununuzu çözecektir:

if(!$scope.$$phase) {
  //TODO
}

(! $ Scope. $$ phase) $ scope. $ Uygula (), bu, $ kapsamınız anlamına gelirse yapmayın.
MGot90
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.