Cara berhenti berlangganan ke acara siaran di angularJS. Cara menghapus fungsi yang didaftarkan melalui $ on


278

Saya telah mendaftarkan pendengar saya ke acara $ broadcast menggunakan fungsi $ on

$scope.$on("onViewUpdated", this.callMe);

dan saya ingin membatalkan pendaftaran pendengar ini berdasarkan aturan bisnis tertentu. Tetapi masalah saya adalah bahwa setelah terdaftar saya tidak dapat membatalkan pendaftarannya.

Apakah ada metode di AngularJS untuk membatalkan pendaftaran pendengar tertentu? Metode seperti $ pada yang membatalkan pendaftaran acara ini, mungkin $ off. Jadi itu berdasarkan logika bisnis yang bisa saya katakan

 $scope.$off("onViewUpdated", this.callMe);

dan fungsi ini berhenti dipanggil ketika seseorang menyiarkan acara "onViewUpdated".

Terima kasih

EDIT : Saya ingin membatalkan registrasi pendengar dari fungsi lain. Bukan fungsi tempat saya mendaftarkannya.


2
Bagi siapa pun yang bertanya-tanya, fungsi yang dikembalikan didokumentasikan di sini
Fagner Brack

Jawaban:


477

Anda perlu menyimpan fungsi yang dikembalikan dan memanggilnya untuk berhenti berlangganan dari acara tersebut.

var deregisterListener = $scope.$on("onViewUpdated", callMe);
deregisterListener (); // this will deregister that listener

Ini ditemukan dalam kode sumber :) setidaknya di 1.0.4. Saya hanya akan memposting kode lengkap karena pendek

/**
  * @param {string} name Event name to listen on.
  * @param {function(event)} listener Function to call when the event is emitted.
  * @returns {function()} Returns a deregistration function for this listener.
  */
$on: function(name, listener) {
    var namedListeners = this.$$listeners[name];
    if (!namedListeners) {
      this.$$listeners[name] = namedListeners = [];
    }
    namedListeners.push(listener);

    return function() {
      namedListeners[indexOf(namedListeners, listener)] = null;
    };
},

Juga, lihat dokumen .


Iya. Setelah men-debug kode sorce saya menemukan bahwa ada array pendengar $$ yang memiliki semua peristiwa dan menciptakan fungsi $ off saya. Terima kasih
Hitesh.Aneja

Apa kasus penggunaan aktual yang Anda tidak bisa menggunakan cara sudut deregistering yang disediakan? Apakah deregistrasi dilakukan dalam lingkup lain tidak terkait dengan ruang lingkup yang menciptakan pendengar?
Liviu T.

1
Ya, saya sebenarnya telah menghapus jawaban saya karena saya tidak ingin membingungkan orang. Ini adalah cara yang tepat untuk melakukan ini.
Ben Lesh

3
@Liviu: Itu akan menjadi sakit kepala dengan aplikasi yang berkembang. Bukan hanya acara ini, ada banyak acara lain juga dan tidak selalu saya selalu membatalkan registrasi dalam fungsi lingkup yang sama. Mungkin ada kasus-kasus ketika saya memanggil fungsi yang mendaftarkan pendengar ini tetapi membatalkan pendaftaran pendengar pada panggilan lain, bahkan saya kasus-kasus saya tidak akan mendapatkan referensi kecuali saya menyimpannya di luar lingkup saya. Jadi untuk implementasi saya saat ini implementasi saya terlihat solusi yang layak untuk saya. Tapi pasti ingin tahu alasan mengapa AngularJS melakukannya dengan cara ini.
Hitesh.Aneja

2
Saya pikir Angular melakukannya dengan cara ini karena banyak waktu fungsi inline anonim digunakan sebagai argumen untuk fungsi $ pada. Untuk memanggil $ scope. $ Off (jenis, fungsi) kita perlu menyimpan referensi ke fungsi anonim. Itu hanya berpikir dengan cara yang berbeda tentang bagaimana seseorang biasanya melakukan menambah / menghapus acara pendengar dalam bahasa seperti ActionScript atau pola yang dapat diamati di Jawa
dannrob

60

Melihat sebagian besar balasan, mereka tampak terlalu rumit. Angular telah membangun mekanisme untuk membatalkan pendaftaran.

Gunakan fungsi deregistrasi yang dikembalikan oleh$on :

// Register and get a handle to the listener
var listener = $scope.$on('someMessage', function () {
    $log.log("Message received");
});

// Unregister
$scope.$on('$destroy', function () {
    $log.log("Unregistering listener");
    listener();
});

Sesederhana ini, ada banyak jawaban tetapi ini lebih ringkas.
David Aguilar

8
Secara teknis benar, meskipun agak menyesatkan, karena $scope.$ontidak harus dibatalkan secara manual $destroy. Contoh yang lebih baik adalah menggunakan a $rootScope.$on.
hgoebl

2
jawaban terbaik tetapi ingin melihat penjelasan lebih lanjut tentang mengapa memanggil pendengar itu di dalam $ hancurkan membunuh pendengarnya.
Mohammad Rafigh

1
@MohammadRafigh Memanggil pendengar di dalam $ destroy adalah tempat saya memilih untuk meletakkannya. Jika saya ingat dengan benar, ini adalah kode yang saya miliki di dalam arahan dan masuk akal bahwa ketika arahan lingkup dihancurkan, pendengar harus tidak terdaftar.
long2know

@ Hgoebl Saya tidak tahu apa yang Anda maksud. Jika saya memiliki, misalnya, arahan yang digunakan di banyak tempat, dan masing-masing mendaftarkan pendengar untuk suatu acara, bagaimana menggunakan $ rootScope. $ On membantu saya? Pembuangan ruang lingkup direktif tampaknya menjadi tempat terbaik untuk membuang pendengarnya.
long2know

26

Kode ini berfungsi untuk saya:

$rootScope.$$listeners.nameOfYourEvent=[];

1
Melihat $ rootScope. $$ pendengar juga merupakan cara yang baik untuk mengamati siklus hidup pendengar, dan untuk bereksperimen dengannya.
XML

Terlihat sederhana dan hebat. Saya pikir itu baru saja menghapus referensi fungsi. bukan?
Jay Shukla

26
Solusi ini tidak disarankan karena anggota pendengar $$ dianggap pribadi. Bahkan, setiap anggota objek sudut dengan awalan '$$' bersifat pribadi oleh konvensi.
shovavnik

5
Saya tidak akan merekomendasikan opsi ini, karena ini menghapus semua pendengar, bukan hanya yang perlu Anda hapus. Ini dapat menyebabkan masalah di masa mendatang ketika Anda menambahkan pendengar lain di bagian lain dari skrip.
Rainer Plumer

10

EDIT: Cara yang benar untuk melakukan ini ada di jawaban @ LiviuT!

Anda selalu dapat memperluas cakupan Angular untuk memungkinkan Anda menghapus pendengar seperti:

//A little hack to add an $off() method to $scopes.
(function () {
  var injector = angular.injector(['ng']),
      rootScope = injector.get('$rootScope');
      rootScope.constructor.prototype.$off = function(eventName, fn) {
        if(this.$$listeners) {
          var eventArr = this.$$listeners[eventName];
          if(eventArr) {
            for(var i = 0; i < eventArr.length; i++) {
              if(eventArr[i] === fn) {
                eventArr.splice(i, 1);
              }
            }
          }
        }
      }
}());

Dan inilah cara kerjanya:

  function myEvent() {
    alert('test');
  }
  $scope.$on('test', myEvent);
  $scope.$broadcast('test');
  $scope.$off('test', myEvent);
  $scope.$broadcast('test');

Dan inilah penyedotnya dalam aksi


Bekerja seperti pesona! tetapi saya sedikit mengeditnya, saya meletakkannya di bagian .run
aimiliano

Cintai solusi ini. Membuat solusi yang jauh lebih bersih - jadi lebih mudah dibaca. +1
Rick

7

Setelah men-debug kode, saya membuat fungsi saya sendiri seperti jawaban "blesh". Jadi ini yang saya lakukan

MyModule = angular.module('FIT', [])
.run(function ($rootScope) {
        // Custom $off function to un-register the listener.
        $rootScope.$off = function (name, listener) {
            var namedListeners = this.$$listeners[name];
            if (namedListeners) {
                // Loop through the array of named listeners and remove them from the array.
                for (var i = 0; i < namedListeners.length; i++) {
                    if (namedListeners[i] === listener) {
                        return namedListeners.splice(i, 1);
                    }
                }
            }
        }
});

jadi dengan melampirkan fungsi saya ke $ rootscope sekarang ini tersedia untuk semua pengontrol saya.

dan dalam kode saya yang saya lakukan

$scope.$off("onViewUpdated", callMe);

Terima kasih

EDIT: Cara AngularJS untuk melakukan ini adalah dalam jawaban @ LiviuT! Tetapi jika Anda ingin membatalkan registrasi pendengar di lingkup lain dan pada saat yang sama ingin menjauh dari membuat variabel lokal untuk menyimpan referensi fungsi de-registrasi. Ini adalah solusi yang memungkinkan.


1
Saya sebenarnya menghapus jawaban saya, karena jawaban @ LiviuT adalah 100% benar.
Ben Lesh

@blesh LiviuT menjawab dengan benar dan secara akut pendekatan yang disediakan angualar untuk membatalkan pendaftaran tetapi tidak terhubung dengan baik untuk skenario di mana Anda harus mendaftar ulang pendengar dalam lingkup yang berbeda. Jadi ini adalah alternatif yang mudah.
Hitesh.Aneja

1
Ini memberikan kait yang sama dengan solusi lain. Anda baru saja meletakkan variabel yang berisi fungsi penghancuran di penutupan eksterior atau bahkan dalam koleksi global ... atau di mana pun Anda inginkan.
Ben Lesh

Saya tidak ingin terus membuat variabel global untuk menyimpan referensi dari fungsi de-registeration dan juga saya tidak melihat masalah dengan menggunakan fungsi $ off saya sendiri.
Hitesh.Aneja

1

@ LiviuT jawabannya luar biasa, tetapi sepertinya membuat banyak orang bertanya-tanya bagaimana cara mengakses kembali fungsi runtuh handler dari $ scope atau fungsi lain, jika Anda ingin menghancurkannya dari tempat lain selain dari tempat ia dibuat. @ Рустем Мусабеков Jawabannya bekerja dengan baik, tetapi tidak terlalu idiomatis. (Dan bergantung pada apa yang seharusnya menjadi detail implementasi pribadi, yang dapat berubah sewaktu-waktu.) Dan dari sana, semakin rumit ...

Saya pikir jawaban yang mudah di sini adalah dengan hanya membawa referensi ke fungsi tear-down ( offCallMeFndalam contohnya) di handler itu sendiri, dan kemudian menyebutnya berdasarkan beberapa kondisi; mungkin argumen yang Anda sertakan pada acara yang Anda $ broadcast atau $ emit. Dengan demikian, penangan dapat menghancurkan diri mereka sendiri, kapan pun Anda mau, di mana pun Anda inginkan, membawa benih kehancuran mereka sendiri. Seperti itu:

// Creation of our handler:
var tearDownFunc = $rootScope.$on('demo-event', function(event, booleanParam) {
    var selfDestruct = tearDownFunc;
    if (booleanParam === false) {
        console.log('This is the routine handler here. I can do your normal handling-type stuff.')
    }
    if (booleanParam === true) {
        console.log("5... 4... 3... 2... 1...")
        selfDestruct();
    }
});

// These two functions are purely for demonstration
window.trigger = function(booleanArg) {
    $scope.$emit('demo-event', booleanArg);
}
window.check = function() {
    // shows us where Angular is stashing our handlers, while they exist
    console.log($rootScope.$$listeners['demo-event'])
};

// Interactive Demo:

>> trigger(false);
// "This is the routine handler here. I can do your normal handling-type stuff."

>> check();
// [function] (So, there's a handler registered at this point.)  

>> trigger(true);
// "5... 4... 3... 2... 1..."

>> check();
// [null] (No more handler.)

>> trigger(false);
// undefined (He's dead, Jim.)

Dua pemikiran:

  1. Ini adalah formula yang bagus untuk penangan run-once. Jatuhkan saja persyaratan dan jalankan selfDestructsegera setelah ia menyelesaikan misi bunuh diri.
  2. Saya bertanya-tanya tentang apakah ruang lingkup asal akan pernah dihancurkan dengan benar dan dikumpulkan, mengingat Anda membawa referensi ke variabel tertutup. Anda harus menggunakan jutaan ini bahkan untuk itu menjadi masalah memori, tapi saya ingin tahu. Jika ada yang punya wawasan, silakan berbagi.

1

Daftarkan pengait untuk berhenti berlangganan pendengar Anda ketika komponen dihapus:

$scope.$on('$destroy', function () {
   delete $rootScope.$$listeners["youreventname"];
});  

Meskipun bukan cara yang diterima secara umum untuk melakukan ini, ada kalanya ini adalah solusi yang diperlukan.
Tony Brasunas

1

Jika Anda perlu menghidupkan dan mematikan pendengar beberapa kali, Anda dapat membuat fungsi dengan booleanparameter

function switchListen(_switch) {
    if (_switch) {
      $scope.$on("onViewUpdated", this.callMe);
    } else {
      $rootScope.$$listeners.onViewUpdated = [];
    }
}

0

'$ on' sendiri mengembalikan fungsi untuk tidak mendaftar

 var unregister=  $rootScope.$on('$stateChangeStart',
            function(event, toState, toParams, fromState, fromParams, options) { 
                alert('state changing'); 
            });

Anda dapat memanggil fungsi unregister () untuk membatalkan registrasi pendengar itu


0

Salah satu caranya adalah menghancurkan pendengar begitu Anda selesai.

var removeListener = $scope.$on('navBarRight-ready', function () {
        $rootScope.$broadcast('workerProfile-display', $scope.worker)
        removeListener(); //destroy the listener
    })
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.