AngularJS: Mencegah kesalahan $ digest sudah berlangsung saat memanggil $ scope. $ Apply ()

838

Saya menemukan bahwa saya perlu memperbarui halaman saya ke lingkup saya secara manual lebih dan lebih sejak membangun aplikasi di sudut.

Satu-satunya cara saya tahu untuk melakukan ini adalah menelepon $apply()dari lingkup pengontrol dan arahan saya. Masalah dengan ini adalah bahwa ia terus melempar kesalahan ke konsol yang berbunyi:

Kesalahan: $ digest sudah dalam proses

Adakah yang tahu bagaimana cara menghindari kesalahan ini atau mencapai hal yang sama tetapi dengan cara yang berbeda?

Bola lampu1
sumber
34
Ini benar-benar membuat frustasi bahwa kita perlu menggunakan $ apply semakin banyak.
OZ_
Saya mendapatkan kesalahan ini juga, meskipun saya menelepon $ terapkan dalam panggilan balik. Saya menggunakan perpustakaan pihak ketiga untuk mengakses data di server mereka, jadi saya tidak bisa memanfaatkan $ http, saya juga tidak mau karena saya harus menulis ulang perpustakaan mereka untuk menggunakan $ http.
Trevor
45
gunakan$timeout()
Onur Yıldırım
6
gunakan $ timeout (fn) +1, Ini dapat memperbaiki masalah, $ lingkup. $$ fase bukan solusi terbaik.
Huei Tan
1
Hanya bungkus kode / ruang lingkup panggilan. $ Berlaku dari dalam batas waktu (bukan $ batas waktu) Fungsi AJAX (bukan $ http) dan acara (tidak ng-*). Pastikan, jika Anda menelepon dari dalam fungsi (yang disebut melalui batas waktu / ajax / peristiwa), bahwa itu tidak juga dijalankan pada beban awalnya.
Patrick

Jawaban:

660

Jangan gunakan pola ini - ini pada akhirnya akan menyebabkan lebih banyak kesalahan daripada yang dipecahkan. Meskipun Anda pikir itu memperbaiki sesuatu, itu tidak.

Anda dapat memeriksa apakah a $digestsudah dalam proses dengan memeriksa $scope.$$phase.

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

$scope.$$phaseakan kembali "$digest"atau "$apply"jika a $digestatau $applysedang berlangsung. Saya percaya perbedaan antara negara-negara ini adalah yang $digestakan memproses jam tangan dari ruang lingkup saat ini dan anak-anaknya, dan $applyakan memproses pengamat semua lingkup.

Untuk titik @ dnc253, jika Anda menemukan diri Anda menelepon $digestatau $applysering, Anda mungkin salah melakukannya. Saya biasanya menemukan saya perlu mencerna ketika saya perlu memperbarui keadaan lingkup sebagai akibat dari peristiwa DOM yang menembak di luar jangkauan Angular. Misalnya, ketika modal bootstrap twitter menjadi tersembunyi. Terkadang acara DOM menyala ketika a $digestsedang berlangsung, kadang tidak. Itu sebabnya saya menggunakan cek ini.

Saya ingin tahu cara yang lebih baik jika ada yang tahu.


Dari komentar: oleh @anddoutoi

angular.js Anti Patterns

  1. Jangan lakukan if (!$scope.$$phase) $scope.$apply(), itu berarti Anda $scope.$apply()tidak cukup tinggi di tumpukan panggilan.
Lee
sumber
230
Sepertinya saya suka $ digest / $ apply harus melakukan ini secara default
Roy Truelove
21
Perhatikan bahwa dalam beberapa kasus saya harus memeriksa tetapi ruang lingkup saat ini DAN ruang lingkup root. Saya telah mendapatkan nilai untuk fase $$ pada root tetapi tidak pada lingkup saya. Pikir itu ada hubungannya dengan ruang lingkup direktif terisolasi, tapi ...
Roy Truelove
106
"Stop doing if (!$scope.$$phase) $scope.$apply()", github.com/angular/angular.js/wiki/Anti-Patterns
anddoutoi
34
@anddoutoi: Setuju; tautan Anda membuatnya cukup jelas, ini bukan solusinya; Namun, saya tidak yakin apa yang dimaksud dengan "Anda tidak cukup tinggi di tumpukan panggilan". Tahukah Anda apa artinya ini?
Trevor
13
@threed: lihat jawabannya oleh aaronfrost. Cara yang benar adalah dengan menggunakan penangguhan untuk memicu intisari pada siklus berikutnya. Kalau tidak, acara akan hilang dan tidak memperbarui ruang lingkup sama sekali.
Marek
663

Dari diskusi baru-baru ini dengan orang-orang Angular tentang topik ini: Untuk alasan pembuktian di masa depan, Anda sebaiknya tidak menggunakannya$$phase

Ketika ditekan untuk cara "benar" untuk melakukannya, jawabannya saat ini

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

Baru-baru ini saya mengalami hal ini ketika menulis layanan sudut untuk membungkus facebook, google, dan API Twitter yang, pada berbagai tingkat, memiliki panggilan balik.

Berikut ini contoh dari dalam layanan. (Demi singkatnya, sisa layanan - yang mengatur variabel, menyuntikkan $ timeout dll - telah ditinggalkan.)

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

Perhatikan bahwa argumen penundaan untuk $ timeout adalah opsional dan akan menjadi 0 jika dibiarkan tidak disetel ( $ timeout memanggil $ browser.defer yang defaultnya ke 0 jika penundaan tidak diatur )

Sedikit tidak intuitif, tapi itu jawaban dari orang-orang yang menulis Angular, jadi itu cukup bagus untukku!

betaorbust
sumber
5
Saya telah menemukan ini berkali-kali dalam arahan saya. Sedang menulis satu untuk redactor dan ini ternyata bekerja dengan sempurna. Saya berada di sebuah pertemuan dengan Brad Green dan dia mengatakan bahwa Angular 2.0 akan menjadi besar tanpa siklus intisari menggunakan kemampuan mengamati asli JS dan menggunakan polyfill untuk browser yang kurang itu. Pada titik itu kita tidak perlu melakukan ini lagi. :)
Michael J. Calkins
Kemarin saya telah melihat masalah ketika memanggil selectize.refreshItems () di dalam $ timeout menyebabkan kesalahan inturve rekursif yang ditakuti. Adakah ide bagaimana itu bisa terjadi?
iwein
3
Jika Anda menggunakan $timeoutdaripada yang asli setTimeout, mengapa Anda tidak menggunakan $windowyang asli window?
LeeGee
2
@ LeeGee: Tujuan penggunaan $timeoutdalam kasus ini, adalah $timeoutmemastikan bahwa cakupan sudut diperbarui dengan benar. Jika $ digest tidak dalam proses, itu akan menyebabkan $ digest baru dijalankan.
awe
2
@webicy Itu bukan hal. Ketika tubuh fungsi dilewatkan ke $ timeout dijalankan, janji sudah terpecahkan! Sama sekali tidak ada alasan untuk cancelitu. Dari dokumen : "Sebagai hasil dari ini, janji itu akan diselesaikan dengan penolakan." Anda tidak bisa menyelesaikan janji yang sudah ditentukan. Pembatalan Anda tidak akan menyebabkan kesalahan, tetapi juga tidak akan melakukan hal positif.
daemonexmachina
324

Siklus digest adalah panggilan sinkron. Itu tidak akan menghasilkan kontrol ke loop acara browser sampai selesai. Ada beberapa cara untuk mengatasinya. Cara termudah untuk mengatasinya adalah dengan menggunakan built in $ timeout, dan cara kedua adalah jika Anda menggunakan garis bawah atau lodash (dan seharusnya), hubungi yang berikut ini:

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

atau jika Anda memiliki lodash:

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

Kami mencoba beberapa solusi, dan kami benci menyuntikkan $ rootScope ke semua pengontrol, arahan, dan bahkan beberapa pabrik kami. Jadi, batas waktu $ dan _.defer telah menjadi favorit kami sejauh ini. Metode-metode ini berhasil memberitahu angular untuk menunggu hingga loop animasi berikutnya, yang akan menjamin bahwa ruang lingkup saat ini. $ Apply sudah berakhir.

sangat dingin
sumber
2
Apakah ini sebanding dengan menggunakan $ timeout (...)? Saya telah menggunakan $ timeout dalam beberapa kasus untuk menunda ke siklus acara berikutnya dan tampaknya berfungsi dengan baik - ada yang tahu jika ada alasan untuk tidak menggunakan $ timeout?
Trevor
9
Ini seharusnya hanya digunakan jika Anda sudah menggunakan underscore.js. Solusi ini tidak layak mengimpor seluruh pustaka garis bawah hanya untuk menggunakan deferfungsinya. Saya lebih suka $timeoutsolusinya karena semua orang sudah memiliki akses $timeoutmelalui sudut, tanpa ada ketergantungan pada perpustakaan lain.
tennisgent
10
Benar ... tetapi jika Anda tidak menggunakan garis bawah atau lodash ... Anda perlu mengevaluasi kembali apa yang Anda lakukan. Kedua lib tersebut telah mengubah cara kode itu terlihat.
sangat dingin
2
Kami memiliki lodash sebagai ketergantungan untuk Restangular (kami akan segera menghapus Restangular demi ng-route). Saya pikir ini jawaban yang bagus tetapi tidak bagus untuk menganggap orang ingin menggunakan garis bawah / lodash. Bagaimanapun juga, lib-lib itu baik-baik saja ... jika Anda cukup menggunakannya ... hari ini saya menggunakan metode ES5 yang menghapus 98% alasan saya menggunakan garis bawah.
BradGreens
2
Anda benar @SgtPooki. Saya memodifikasi jawaban untuk memasukkan opsi untuk menggunakan $ timeout juga. $ timeout dan _.defer akan menunggu hingga loop animasi berikutnya, yang akan memastikan bahwa ruang lingkup saat ini. $ apply telah berakhir. Terima kasih telah membuat saya jujur, dan membuat saya memperbarui jawabannya di sini.
sangat dingin
267

Banyak jawaban di sini berisi saran yang bagus tetapi juga dapat menyebabkan kebingungan. Hanya menggunakan $timeoutadalah tidak yang terbaik atau solusi yang tepat. Juga, pastikan untuk membaca bahwa jika Anda khawatir dengan penampilan atau skalabilitas.

Hal-hal yang harus Anda ketahui

  • $$phase bersifat pribadi untuk kerangka kerja dan ada alasan bagus untuk itu.

  • $timeout(callback)akan menunggu sampai siklus intisari saat ini (jika ada) selesai, kemudian jalankan callback, lalu jalankan di akhir penuh $apply.

  • $timeout(callback, delay, false)akan melakukan hal yang sama (dengan penundaan opsional sebelum menjalankan panggilan balik), tetapi tidak akan memunculkan $apply(argumen ketiga) yang menyimpan kinerja jika Anda tidak mengubah model Angular Anda ($ lingkup).

  • $scope.$apply(callback) memohon, antara lain, $rootScope.$digest ,, yang berarti itu akan mereduksi ruang lingkup root aplikasi dan semua anak-anaknya, bahkan jika Anda berada dalam ruang lingkup yang terisolasi.

  • $scope.$digest()hanya akan menyinkronkan modelnya ke tampilan, tetapi tidak akan mencerna ruang lingkup orang tuanya, yang dapat menyimpan banyak kinerja ketika bekerja pada bagian yang terisolasi dari HTML Anda dengan ruang lingkup yang terisolasi (sebagian besar dari direktif). $ digest tidak menerima panggilan balik: Anda mengeksekusi kode, lalu mencernanya.

  • $scope.$evalAsync(callback)telah diperkenalkan dengan angularjs 1.2, dan mungkin akan menyelesaikan sebagian besar masalah Anda. Silakan merujuk paragraf terakhir untuk mempelajari lebih lanjut tentang hal itu.

  • jika Anda mendapatkannya $digest already in progress error, maka arsitektur Anda salah: Anda tidak perlu memperbaiki ruang lingkup Anda, atau Anda tidak harus bertanggung jawab atas itu (lihat di bawah).

Cara menyusun kode Anda

Ketika Anda mendapatkan kesalahan itu, Anda mencoba untuk mencerna ruang lingkup Anda saat ini sedang dalam proses: karena Anda tidak tahu keadaan ruang lingkup Anda pada saat itu, Anda tidak bertanggung jawab menangani pencernaannya.

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

Dan jika Anda tahu apa yang Anda lakukan dan bekerja pada arahan kecil yang terisolasi sementara bagian dari aplikasi Angular besar, Anda bisa lebih suka $ digest daripada lebih dari $ berlaku untuk menyimpan kinerja.

Pembaruan sejak Angularjs 1.2

Metode baru yang kuat telah ditambahkan ke $ scope apa saja: $evalAsync . Pada dasarnya, ia akan menjalankan callbacknya dalam siklus digest saat ini jika ada, jika tidak, siklus digest baru akan mulai mengeksekusi callback.

Itu masih tidak sebagus $scope.$digestjika Anda benar-benar tahu bahwa Anda hanya perlu menyinkronkan bagian yang terisolasi dari HTML Anda (karena yang baru $applyakan dipicu jika tidak ada yang sedang berlangsung), tetapi ini adalah solusi terbaik ketika Anda menjalankan fungsi yang Anda tidak dapat mengetahuinya apakah akan dijalankan secara sinkron atau tidak , misalnya setelah mengambil sumber daya yang berpotensi di-cache: kadang-kadang ini membutuhkan panggilan async ke server, jika tidak sumber daya akan diambil secara lokal secara sinkron.

Dalam kasus ini dan semua yang lainnya di mana Anda memiliki !$scope.$$phase, pastikan untuk menggunakannya$scope.$evalAsync( callback )

floribon
sumber
4
$timeoutdikritik secara sepintas. Bisakah Anda memberi lebih banyak alasan untuk dihindari $timeout?
mlhDev
88

Metode pembantu kecil yang berguna untuk menjaga proses ini KERING:

function safeApply(scope, fn) {
    (scope.$$phase || scope.$root.$$phase) ? fn() : scope.$apply(fn);
}
lambinator
sumber
6
SafeApply Anda membantu saya memahami apa yang sedang terjadi lebih dari apa pun. Terima kasih telah memposting itu.
Jason More
4
Saya akan melakukan hal yang sama, tetapi tidak melakukan ini berarti ada kemungkinan perubahan yang kita buat di fn () tidak akan terlihat oleh $ digest? Bukankah lebih baik menunda fungsi, dengan asumsi ruang lingkupnya. $$ phase === '$ digest'?
Spencer Alger
Saya setuju, terkadang $ apply () digunakan untuk memicu intisari, hanya memanggil fn dengan sendirinya ... bukankah itu akan menghasilkan masalah?
CMCDragonkai
1
Saya merasa seperti scope.$apply(fn);seharusnya scope.$apply(fn());karena fn () akan menjalankan fungsi dan bukan fn. Tolong bantu saya ke tempat saya salah
madhu131313
1
@ZenOut Panggilan ke $ apply mendukung berbagai jenis argumen, termasuk fungsi. Jika melewati fungsi, itu mengevaluasi fungsi.
boxmein
33

Saya memiliki masalah yang sama dengan skrip pihak ketiga seperti CodeMirror misalnya dan Krpano, dan bahkan menggunakan metode safeApply yang disebutkan di sini belum memecahkan kesalahan bagi saya.

Tapi yang harus diselesaikan adalah menggunakan layanan $ timeout (jangan lupa menyuntikkannya terlebih dahulu)

Jadi, sesuatu seperti:

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

dan jika di dalam kode Anda yang Anda gunakan

ini

mungkin karena itu di dalam pengontrol direktif pabrik atau hanya perlu semacam ikatan, maka Anda akan melakukan sesuatu seperti:

.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
sumber
32

Lihat http://docs.angularjs.org/error/$rootScope:inprog

Masalah muncul ketika Anda memiliki panggilan ke $applyyang kadang-kadang dijalankan secara asinkron di luar kode Angular (ketika $ berlaku harus digunakan) dan kadang-kadang sinkron dalam kode Angular (yang menyebabkan$digest already in progress kesalahan).

Ini dapat terjadi, misalnya, ketika Anda memiliki perpustakaan yang secara tidak sinkron mengambil item dari server dan menyimpannya. Pertama kali suatu item diminta, itu akan diambil secara tidak sinkron agar tidak memblokir eksekusi kode. Namun, kedua kalinya item tersebut sudah ada dalam cache sehingga dapat diambil secara serempak.

Cara untuk mencegah kesalahan ini adalah dengan memastikan bahwa kode yang dipanggil $applydijalankan secara tidak sinkron. Ini dapat dilakukan dengan menjalankan kode Anda di dalam panggilan $timeoutdengan penundaan diatur ke 0(yang merupakan default). Namun, memanggil kode Anda di dalam $timeoutmenghilangkan keharusan untuk menelepon $apply, karena $ timeout akan memicu $digestsiklus lain sendiri, yang pada gilirannya akan melakukan semua pembaruan yang diperlukan, dll.

Larutan

Singkatnya, alih-alih melakukan ini:

... your controller code...

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

... more of your controller code...

melakukan hal ini:

... your controller code...

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

... more of your controller code...

Hanya panggilan $apply bila Anda tahu kode yang menjalankannya akan selalu dijalankan di luar kode Angular (mis. Panggilan Anda ke $ apply akan terjadi di dalam callback yang dipanggil oleh kode di luar kode Angular Anda).

Kecuali jika seseorang menyadari adanya kerugian yang merugikan $timeoutakibat penggunaan yang berlebihan $apply, saya tidak melihat mengapa Anda tidak selalu dapat menggunakan $timeout(dengan nol penundaan) $apply, karena hal itu akan melakukan hal yang kira-kira sama.

Trevor
sumber
Terima kasih, ini berhasil untuk kasus saya di mana saya tidak memanggil $applydiri saya tetapi masih mendapatkan kesalahan.
ariscris
5
Perbedaan utama adalah yang $applysinkron (callback dieksekusi, maka kode berikut $ berlaku) sementara $timeouttidak: kode saat ini setelah batas waktu dieksekusi, maka tumpukan baru dimulai dengan callbacknya, seolah-olah Anda sedang menggunakan setTimeout. Itu dapat menyebabkan gangguan grafis jika Anda memperbarui dua kali model yang sama: $timeoutakan menunggu tampilan menjadi segar sebelum memperbaruinya lagi.
floribon
Terima kasih, threed. Saya memiliki metode yang disebut sebagai hasil dari aktivitas $ watch, dan sedang mencoba memperbarui UI sebelum filter eksternal saya selesai dieksekusi. Menempatkannya di dalam fungsi batas waktu $ bekerja untuk saya.
djmarquette
28

Ketika Anda mendapatkan kesalahan ini, itu pada dasarnya berarti sudah dalam proses memperbarui tampilan Anda. Anda benar-benar tidak perlu memanggil $apply()dalam controller Anda. Jika tampilan Anda tidak diperbarui seperti yang Anda harapkan, dan kemudian Anda mendapatkan kesalahan ini setelah menelepon $apply(), kemungkinan besar Anda tidak memperbarui model dengan benar. Jika Anda memposting beberapa hal spesifik, kami dapat menemukan masalah intinya.

dnc253
sumber
heh, saya menghabiskan sepanjang hari untuk mengetahui bahwa AngularJS tidak bisa menonton binding "secara ajaib" dan kadang-kadang saya harus mendorongnya dengan $ apply ().
OZ_
apa artinya you're not updating the the model correctly? $scope.err_message = 'err message';apakah pembaruan tidak benar?
OZ_
2
Satu-satunya waktu yang Anda butuhkan untuk menelepon $apply()adalah ketika Anda memperbarui model "luar" sudut (misalnya dari plugin jQuery). Sangat mudah untuk jatuh ke dalam jebakan pandangan yang tidak terlihat benar, dan jadi Anda melempar banyak $apply()di mana-mana, yang kemudian berakhir dengan kesalahan yang terlihat di OP. Ketika saya mengatakan you're not updating the the model correctlymaksud saya semua logika bisnis tidak mengisi dengan benar apa pun yang mungkin ada dalam ruang lingkup, yang mengarah ke tampilan yang tidak tampak seperti yang diharapkan.
dnc253
@ dnc253 Saya setuju, dan saya menulis jawabannya. Mengetahui apa yang saya ketahui sekarang, saya akan menggunakan $ timeout (function () {...}); Itu melakukan hal yang sama seperti _.defer tidak. Keduanya tunduk pada loop animasi berikutnya.
sangat dingin
14

Bentuk aman terpendek $applyadalah:

$timeout(angular.noop)
Ahli sihir
sumber
11

Anda juga dapat menggunakan evalAsync. Ini akan berjalan beberapa saat setelah digest selesai!

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

Pertama-tama, jangan perbaiki dengan cara ini

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

Itu tidak masuk akal karena $ phase hanyalah flag boolean untuk siklus $ digest, jadi $ apply Anda () terkadang tidak akan berjalan. Dan ingat itu praktik yang buruk.

Sebaliknya, gunakan $timeout

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

Jika Anda menggunakan garis bawah atau lodash, Anda dapat menggunakan defer ():

_.defer(function(){ 
  $scope.$apply(); 
});
Sagar M
sumber
9

Terkadang Anda masih akan mendapatkan kesalahan jika Anda menggunakan cara ini ( https://stackoverflow.com/a/12859093/801426 ).

Coba ini:

if(! $rootScope.$root.$$phase) {
...
banteng
sumber
5
menggunakan keduanya! $ scope. $$ phase dan! $ scope. $ root. $$ phase (tidak! $ rootScope. $ root. $$ phase) bekerja untuk saya. +1
asprotte
2
$rootScopedan anyScope.$rootorang yang sama. $rootScope.$rootberlebihan.
floribon
5

coba gunakan

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

dari pada

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

$ applyAsync Jadwalkan permohonan $ apply untuk terjadi di lain waktu. Ini dapat digunakan untuk mengantri banyak ekspresi yang perlu dievaluasi dalam intisari yang sama.

CATATAN: Di dalam $ digest, $ applyAsync () hanya akan memerah jika lingkup saat ini adalah $ rootScope. Ini berarti bahwa jika Anda memanggil $ digest pada lingkup anak, itu tidak akan secara otomatis memunculkan antrian $ applyAsync ().

Exmaple:

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

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

Referensi:

1. Cakupan. $ ApplyAsync () vs. Cakupan. $ EvalAsync () di AngularJS 1.3

  1. Dokumen AngularJs
Eduardo Eljaiek
sumber
4

Saya akan menyarankan Anda untuk menggunakan acara khusus daripada memicu siklus intisari.

Saya datang untuk mengetahui bahwa menyiarkan acara khusus dan mendaftarkan pendengar untuk acara ini adalah solusi yang baik untuk memicu tindakan yang Anda inginkan terjadi baik saat Anda sedang dalam siklus intisari maupun tidak.

Dengan membuat acara khusus, Anda juga menjadi lebih efisien dengan kode Anda karena Anda hanya memicu pendengar yang berlangganan acara tersebut dan TIDAK memicu semua jam tangan yang terikat pada ruang lingkup seperti yang Anda lakukan jika Anda menggunakan ruang lingkup. $ Terapkan.

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


$scope.$broadcast('customEventName', optionalCustomEventArguments);
nelsonomuto
sumber
3

yearofmoo melakukan pekerjaan yang hebat dalam menciptakan fungsi $ safe yang dapat digunakan kembali untuk kita:

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

Penggunaan:

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

Saya sudah bisa menyelesaikan masalah ini dengan menelepon $evalalih-alih $applydi tempat-tempat di mana saya tahu bahwa $digestfungsinya akan berjalan.

Menurut dokumen , $applypada dasarnya melakukan ini:

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

Dalam kasus saya, suatu ng-clickperubahan variabel dalam lingkup, dan $ menonton pada variabel itu mengubah variabel lain yang harus $applied. Langkah terakhir ini menyebabkan kesalahan "intisari sudah dalam proses".

Dengan mengganti $applydengan $evaldi dalam ekspresi arloji, variabel ruang lingkup diperbarui seperti yang diharapkan.

Oleh karena itu, tampaknya jika intisari akan berjalan karena beberapa perubahan lain dalam Angular, $evalitulah yang perlu Anda lakukan.

teleclimber
sumber
2

gunakan $scope.$$phase || $scope.$apply();saja

Visakh B Sujathan
sumber
1

Memahami bahwa dokumen sudut panggilan memeriksa $$phasesebuah anti-pola , saya mencoba untuk mendapatkan $timeoutdan_.defer bekerja.

Metode batas waktu dan yang ditangguhkan membuat flash {{myVar}}konten yang tidak diparsing dalam dom seperti FOUT . Bagi saya ini tidak dapat diterima. Itu membuat saya tanpa banyak diberitahu dogmatis bahwa ada sesuatu yang hack, dan tidak memiliki alternatif yang cocok.

Satu-satunya hal yang berfungsi setiap saat adalah:

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

Saya tidak mengerti bahaya dari metode ini, atau mengapa ini digambarkan sebagai peretasan oleh orang-orang di komentar dan tim sudut. Perintah itu tampaknya tepat dan mudah dibaca:

"Lakukan intisari kecuali sudah terjadi"

Dalam CoffeeScript itu bahkan lebih cantik:

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

Apa masalahnya dengan ini? Apakah ada alternatif yang tidak akan membuat FOUT? $ safeApply terlihat bagus tetapi menggunakan $$phasemetode inspeksi juga.

SimplGy
sumber
1
Saya ingin melihat respons yang terinformasi untuk pertanyaan ini!
Ben Wheeler
Ini adalah hack karena itu berarti Anda kehilangan konteks atau tidak memahami kode pada titik ini: apakah Anda berada dalam siklus intisari sudut dan Anda tidak memerlukannya, atau Anda secara tidak sinkron di luar itu dan kemudian Anda membutuhkannya. Jika Anda tidak dapat mengetahuinya pada titik kode tersebut, maka Anda tidak bertanggung jawab untuk mencernanya
floribon
1

Ini adalah layanan utilitas saya:

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

dan ini adalah contoh untuk penggunaannya:

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
sumber
1

Saya telah menggunakan metode ini dan tampaknya berfungsi dengan baik. Ini hanya menunggu waktu siklus selesai dan kemudian memicu apply(). Cukup panggil fungsi apply(<your scope>)dari mana saja Anda inginkan.

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);
  }
}
Ashu
sumber
1

Ketika saya menonaktifkan debugger, kesalahan tidak terjadi lagi. Dalam kasus saya , itu karena debugger menghentikan eksekusi kode.

Jmojico
sumber
0

mirip dengan jawaban di atas tetapi ini telah bekerja dengan setia untuk saya ... dalam sebuah layanan tambahkan:

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

Kamu bisa menggunakan

$timeout

untuk mencegah kesalahan.

 $timeout(function () {
                        var scope = angular.element($("#myController")).scope();
                        scope.myMethod();
                        scope.$scope();
                    },1);
Satish Singh
sumber
Bagaimana jika saya tidak ingin menggunakan $ timeout
rahim.nagori
0

Masalah ini pada dasarnya datang ketika, kami meminta sudut untuk menjalankan siklus intisari meskipun dalam proses yang menciptakan masalah untuk sudut ke pemahaman. pengecualian konsekuensi di konsol.
1. Tidak masuk akal untuk memanggil ruang lingkup. $ Apply () di dalam fungsi $ timeout karena secara internal ia melakukan hal yang sama.
2. Kode berjalan dengan fungsi vanilla JavaScript karena asalnya tidak angular angular didefinisikan yaitu setTimeout
3. Untuk melakukan itu Anda dapat menggunakan

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

}); }

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

Ini solusi yang bagus untuk menghindari kesalahan ini dan menghindari $ apply

Anda dapat menggabungkan ini dengan debounce (0) jika memanggil berdasarkan peristiwa eksternal. Di atas adalah 'debounce' yang kami gunakan, dan contoh lengkap kode

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

dan kode itu sendiri untuk mendengarkan suatu peristiwa dan memanggil $ digest hanya pada $ scope yang Anda butuhkan

        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();
        });
Sergey Sahakyan
sumber
-3

Ditemukan ini: https://coderwall.com/p/ngisma di mana Nathan Walker (dekat bagian bawah halaman) menyarankan seorang dekorator dalam $ rootScope untuk membuat func 'safeApply', kode:

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;
      }
    ]);
  }
]);
Warren Davis
sumber
-7

Ini akan menyelesaikan masalah Anda:

if(!$scope.$$phase) {
  //TODO
}
eebbesen
sumber
Jangan lakukan jika (! $ Scope. $$ phase) $ scope. $ Apply (), itu berarti $ scope Anda. $ Apply () tidak cukup tinggi di tumpukan panggilan.
MGot90