Neden if (! $ Kapsam. $$ faz) $ kapsam. $ Apply () bir anti-desen kullanıyor?


92

Bazen kodumda kullanmam gerekiyor $scope.$applyve bazen "zaten devam ediyor" hatası veriyor. Bu yüzden, bunun etrafında bir yol bulmaya başladım ve şu soruyu buldum: AngularJS: $ kapsamını çağırırken zaten devam eden $ özet hatasını önle. $ Apply () . Ancak yorumlarda (ve açısal wiki'de) şunları okuyabilirsiniz:

Bunu yapmayın (! $ Kapsam. $$ aşama) $ kapsam. $ Apply (), bu, $ kapsamınızın. $ Apply () çağrı yığınında yeterince yüksek olmadığı anlamına gelir.

Şimdi iki sorum var:

  1. Bu tam olarak neden bir anti-modeldir?
  2. $ Scope'u güvenle nasıl kullanabilirim. $ Apply?

"Özet zaten devam ediyor" hatasını önlemek için başka bir "çözüm" $ zaman aşımı kullanıyor gibi görünüyor:

$timeout(function() {
  //...
});

Gitmenin yolu bu mu? Daha güvenli mi? İşte asıl soru şu: "Halihazırda devam etmekte olan bir özet" hatası olasılığını tamamen nasıl ortadan kaldırabilirim?

Not: Eşzamanlı olmayan açısal olmayan js geri aramalarında yalnızca $ kapsam kullanıyorum. $ Apply. (bildiğim kadarıyla bunlar $ kapsam kullanmanız gereken durumlar. Değişikliklerinizin uygulanmasını istiyorsanız $ uygulayın)


Benim deneyimlerime göre, scopeaçısal içeriden mi yoksa açısal dışından mı manipüle ediyorsan her zaman bilmelisin . Yani buna göre aramanız gerekip gerekmediğini her zaman bilirsiniz scope.$apply. Ve hem açısal hem de açısal olmayan scopemanipülasyon için aynı kodu kullanıyorsanız, yanlış yapıyorsunuz, her zaman ayrılmalıdır ... yani temelde kontrol etmeniz gereken bir durumla karşılaşırsanız scope.$$phase, kodunuz doğru şekilde tasarlanmıştır ve bunu 'doğru şekilde' yapmanın her zaman bir yolu vardır
doodeec

1
bunu sadece açısal olmayan geri aramalarda kullanıyorum (!) Bu yüzden kafam karıştı
Dominik Goltermann

2
açısal değilse digest already in progresshata
vermez

1
Bende böyle düşünmüştüm. Mesele şu ki: her zaman hatayı atmıyor. Sadece arada bir. Şüphem, başvurunun tesadüfen başka bir özetle çakışmasıdır. Mümkün mü?
Dominik Goltermann

Geri arama kesinlikle açısal değilse bunun mümkün olduğunu sanmıyorum
doodeec

Yanıtlar:


113

Biraz daha kazı yaptıktan sonra, her zaman kullanmanın güvenli olup olmadığı sorusunu çözebildim $scope.$apply. Kısa cevap evet.

Uzun cevap:

Tarayıcınızın Javascript'i nasıl çalıştırdığından dolayı, iki özet çağrısının tesadüfen çakışması mümkün değildir .

Yazdığımız JavaScript kodunun tamamı tek seferde çalışmaz, bunun yerine sırayla çalışır. Bu dönüşlerin her biri baştan sona kesintisiz olarak çalışır ve bir dönüş çalışırken tarayıcımızda başka hiçbir şey olmaz. ( http://jimhoskins.com/2012/12/17/angularjs-and-apply.html adresinden )

Bu nedenle "özet zaten devam ediyor" hatası yalnızca bir durumda ortaya çıkabilir: Başka bir $ apply içinde bir $ apply yayınlandığında, örneğin:

$scope.apply(function() {
  // some code...
  $scope.apply(function() { ... });
});

Bu durum olabilir değil ortaya eğer biz örneğin gibi, saf olmayan angularjs geri aramasında scope.apply arasında geri arama $ kullanın setTimeout. Aşağıdaki kod% 100 kurşun geçirmez olup Yani orada hiçbir bir yapmaya gerekif (!$scope.$$phase) $scope.$apply()

setTimeout(function () {
    $scope.$apply(function () {
        $scope.message = "Timeout called!";
    });
}, 2000);

bu bile güvenli:

$scope.$apply(function () {
    setTimeout(function () {
        $scope.$apply(function () {
            $scope.message = "Timeout called!";
        });
    }, 2000);
});

Nedir DEĞİL güvenli ($ zaman aşımı nedeniyle - tüm angularjs yardımcıları gibi - zaten çağırır $scope.$applysizin için):

$timeout(function () {
    $scope.$apply(function () {
        $scope.message = "Timeout called!";
    });
}, 2000);

Bu aynı zamanda kullanımının neden if (!$scope.$$phase) $scope.$apply()bir anti-model olduğunu da açıklar . $scope.$applyDoğru şekilde kullanırsanız buna ihtiyacınız yoktur : Örneğin, saf bir js geri aramasında setTimeout.

Daha ayrıntılı açıklama için http://jimhoskins.com/2012/12/17/angularjs-and-apply.html okuyun .


Burada bir hizmet oluşturduğum bir $document.bind('keydown', function(e) { $rootScope.$apply(function() { // a passed through function from the controller gets executed here }); });örneğim var, neden burada $ application yapmam gerektiğini gerçekten bilmiyorum, çünkü $ document.bind kullanıyorum ..
Betty St

çünkü $ document yalnızca "Tarayıcının window.document nesnesi için bir jQuery veya jqLite sarmalayıcısı." ve şu şekilde uygulandı: function $DocumentProvider(){ this.$get = ['$window', function(window){ return jqLite(window.document); }]; }Orada hiçbir uygulama yok.
Dominik Goltermann

11
$timeoutanlamsal olarak bir gecikmeden sonra kod çalıştırmak anlamına gelir. İşlevsel olarak güvenli bir şey olabilir, ancak bu bir hack'tir. Bir $digestdöngünün devam edip etmediğini bilemediğinizde veya zaten bir $apply.
John Strickler

1
Kötü olmasının bir başka nedeni: genel api'nin parçası olmayan dahili değişkenler ($$ fazı) kullanır ve bunlar daha yeni bir açısal sürümde değiştirilebilir ve bu nedenle kodunuzu bozabilir. Eşzamanlı olay tetiklemesiyle ilgili sorununuz ilginç olsa da
Dominik Goltermann

4
Daha yeni yaklaşım, mümkünse veya sonraki döngüde geçerli özet döngüsünde güvenli bir şekilde yürüten $ kapsam. Bennadel.com/blog/… adresine
jaymjarri

16

Bu artık kesinlikle bir anti-modeldir. $$ aşamasını kontrol etseniz bile bir sindirim patlaması gördüm. Öneklerle gösterilen dahili API'ye erişmeniz gerekmiyor $$.

Kullanmalısın

 $scope.$evalAsync();

Angular ^ 1.4'te tercih edilen yöntem olduğundan ve özellikle uygulama katmanı için bir API olarak ortaya çıkar.


9

Her durumda, özetiniz devam ederken ve başka bir hizmeti sindirmesi için zorladığınızda, sadece bir hata verir, yani zaten devam eden özet. Yani bunu iyileştirmek için iki seçeneğiniz var. yoklama gibi devam eden başka bir özet olup olmadığını kontrol edebilirsiniz.

Birincisi

if ($scope.$root.$$phase != '$apply' && $scope.$root.$$phase != '$digest') {
    $scope.$apply();
}

yukarıdaki koşul doğruysa, $ kapsamınızı uygulayabilirsiniz. $ diğerlerini uygula değil ve

ikinci çözüm $ zaman aşımı kullanmaktır

$timeout(function() {
  //...
})

diğer özetin $ zaman aşımı tamamlanıncaya kadar başlamasına izin vermeyecektir.


1
olumsuz oy verildi; Soru özellikle, burada anlattığınız şeyi neden YAPMAMANIZ gerektiğini sorar, etrafından dolaşmanın başka bir yolunu değil. Ne zaman kullanılacağını öğrenmek için @gaul'un mükemmel cevabına bakın $scope.$apply();.
PureSpider

Soruyu cevaplamasa da: $timeoutanahtar bu! işe yarıyor ve daha sonra da tavsiye edildiğini buldum.
Himel Nag Rana

Ben sana iyi uygulama yapısını yoksa 2 yıl sonra bu kadar yorum eklemek, ancak bu performans çok fazla mal olabilir gibi çok fazla $ aşımı kullanırken dikkatli olmak oldukça geç olduğunu biliyorum
cpoDesign

9

scope.$apply$digest2 yönlü veri bağlamanın temelini oluşturan bir döngüyü tetikler

Bir $digestdöngü , değerlerinin değişip değişmediğini değerlendirmek $watchiçin eklenen nesneleri, yani modelleri (kesin olarak ) kontrol eder $scopeve bir değişiklik tespit ederse görünümü güncellemek için gerekli adımları atar.

Şimdi kullandığınızda, $scope.$apply"Zaten devam ediyor" hatasıyla karşılaşırsınız, bu nedenle bir $ özetin çalıştığı oldukça açıktır, ancak bunu tetikleyen nedir?

ans -> her $httpçağrı, tüm ng-tıklama, tekrarlama, gösterme, gizleme vb. bir $digestdöngüyü tetikler VE HER KAPSAMIN EN KÖTÜ BÖLÜMÜNDE ÇALIŞIR.

Örneğin, sayfanızda 4 denetleyici veya A, B, C, D yönergesi olduğunu söyleyin

Her birinde 4 $scopemülkünüz varsa , sayfanızda toplam 16 $ kapsam özelliği vardır.

$scope.$applyKontrolör D'de tetiklerseniz , bir $digestdöngü tüm 16 değeri kontrol edecektir !!! artı tüm $ rootScope özellikleri.

Cevap -> ancak $scope.$digestbir $digestalt ve aynı kapsamı tetikler , böylece yalnızca 4 özelliği kontrol eder. Bu nedenle, D'deki değişikliklerin A, B, C'yi etkilemeyeceğinden eminseniz $scope.$digest kullanmayın $scope.$apply.

Bu nedenle, yalnızca bir ng-tıklama veya ng-göster / gizle $digest, kullanıcı herhangi bir olay başlatmamış olsa bile 100'den fazla özellikte bir döngüyü tetikliyor olabilir !


2
Evet maalesef projeye bu kadar geç fark ettim. Bunu baştan bilseydim Angular'ı kullanmazdım. Tüm standart direktifler, TÜM kapsamlarda kirli kontroller gerçekleştiren $ rootScope. Bana sorarsan kötü tasarım kararı. Hangi kapsamların kirli olarak kontrol edilmesi gerektiğini kontrol etmeliyim, çünkü VERİLERİN BU KAPSAMLARLA NASIL BAĞLANTILI OLDUĞUNU BİLİYORUM!
MoonStom

0

Kullanın $timeout, önerilen yoldur.

Benim senaryom, bir WebSocket'ten aldığım verilere göre sayfadaki öğeleri değiştirmem gerektiğidir. Ve $ zaman aşımı olmadan Angular'ın dışında olduğu için, tek model değiştirilecek ancak görünüm değişmeyecek. Çünkü Angular, bu veri parçasının değiştiğini bilmiyor. $timeouttemelde Angular'a bir sonraki $ Digest turunda değişikliği yapmasını söylüyor.

Aşağıdakileri de denedim ve işe yarıyor. Benim için fark, $ zaman aşımının daha net olması.

setTimeout(function(){
    $scope.$apply(function(){
        // changes
    });
},0)

Soket kodunuzu $ apply içine almak daha temizdir (AJAX kodundaki Angular'ınki gibi $http). Aksi takdirde bu kodu her yerde tekrarlamanız gerekir.
timruffles

bu kesinlikle tavsiye edilmez. Ayrıca, $ kapsamın $$ aşaması varsa bunu yaparken bazen bir hata alırsınız. bunun yerine $ kapsam kullanmalısınız. $ evalAsync ();
FlavourScape

Gerek yoktur $scope.$applykullandığınız takdirde setTimeoutya$timeout
Kunal

-1

Çok güzel bir çözüm buldum:

.factory('safeApply', [function($rootScope) {
    return function($scope, fn) {
        var phase = $scope.$root.$$phase;
        if (phase == '$apply' || phase == '$digest') {
            if (fn) {
                $scope.$eval(fn);
            }
        } else {
            if (fn) {
                $scope.$apply(fn);
            } else {
                $scope.$apply();
            }
        }
    }
}])

ihtiyacınız olan yere enjekte edin:

.controller('MyCtrl', ['$scope', 'safeApply',
    function($scope, safeApply) {
        safeApply($scope); // no function passed in
        safeApply($scope, function() { // passing a function in
        });
    }
])
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.