วิธียกเลิกการสมัครรับข้อมูลการออกอากาศใน angularJS วิธีลบฟังก์ชั่นที่ลงทะเบียนผ่านทาง $ on


278

ฉันได้ลงทะเบียนผู้ฟังของฉันไปยังเหตุการณ์ $ broadcast โดยใช้ $ on function

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

และฉันต้องการยกเลิกการลงทะเบียนผู้ฟังนี้ตามกฎทางธุรกิจเฉพาะ แต่ปัญหาของฉันคือเมื่อลงทะเบียนแล้วฉันไม่สามารถยกเลิกการลงทะเบียนได้

มีวิธีการใดใน AngularJS ที่จะยกเลิกการลงทะเบียนผู้ฟังหรือไม่? วิธีการเช่น $ สำหรับการยกเลิกการลงทะเบียนกิจกรรมนี้อาจเป็น $ off เพื่อให้เป็นไปตามตรรกะทางธุรกิจที่ฉันสามารถพูดได้

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

และฟังก์ชั่นนี้หยุดถูกเรียกเมื่อมีคนออกอากาศเหตุการณ์ "onViewUpdated"

ขอบคุณ

แก้ไข : ฉันต้องการยกเลิกการลงทะเบียนฟังจากฟังก์ชั่นอื่น ไม่ใช่ฟังก์ชั่นที่ฉันลงทะเบียน


2
สำหรับทุกคนที่สงสัยว่าฟังก์ชันที่ส่งคืนจะถูกบันทึกไว้ที่นี่
Fagner Brack

คำตอบ:


477

คุณต้องจัดเก็บฟังก์ชันที่ส่งคืนและเรียกใช้เพื่อยกเลิกการสมัครจากกิจกรรม

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

พบได้ในซอร์สโค้ด :) อย่างน้อยใน 1.0.4 ฉันจะโพสต์รหัสเต็มตั้งแต่สั้น

/**
  * @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;
    };
},

ยังดูเอกสาร


ใช่. หลังจากตรวจแก้จุดบกพร่องรหัส sorce ฉันพบว่ามี array ฟัง $$ ที่มีเหตุการณ์ทั้งหมดและสร้างฟังก์ชั่น $ off ของฉัน ขอบคุณ
Hitesh.Aneja

กรณีการใช้งานจริงคืออะไรที่คุณไม่สามารถใช้วิธียกเลิกการลงทะเบียนที่ให้ไว้ได้? การลงทะเบียนทำในขอบเขตอื่นไม่ได้เชื่อมโยงกับขอบเขตที่สร้างผู้ฟังหรือไม่?
Liviu T.

1
ใช่ฉันลบคำตอบของฉันไปแล้วเพราะฉันไม่ต้องการให้คนอื่นสับสน นี่เป็นวิธีที่เหมาะสมในการทำเช่นนี้
เบ็นเลช

3
@Liviu: นั่นจะกลายเป็นปวดหัวกับแอปพลิเคชันที่กำลังเติบโต มันไม่เพียง แต่เหตุการณ์นี้มีกิจกรรมอื่นอีกมากมายเช่นกันและไม่จำเป็นว่าฉันจะต้องยกเลิกการลงทะเบียนในฟังก์ชันขอบเขตเดียวกันเสมอ อาจมีบางกรณีที่ฉันกำลังเรียกใช้ฟังก์ชั่นที่กำลังลงทะเบียนฟังนี้ แต่การยกเลิกการลงทะเบียนฟังในการโทรอื่นแม้ว่าฉันจะไม่ได้รับการอ้างอิงนอกเสียจากว่าฉันจะเก็บไว้นอกขอบเขตของฉัน ดังนั้นสำหรับการติดตั้งในปัจจุบันการติดตั้งของฉันจึงดูเป็นทางออกสำหรับฉัน แต่แน่นอนอยากรู้เหตุผลว่าทำไม AngularJS ทำเช่นนี้
Hitesh.Aneja

2
ฉันคิดว่า Angular ทำเช่นนี้เพราะมีการใช้ฟังก์ชั่นไม่ระบุชื่อแบบอินไลน์เป็นข้อโต้แย้งกับฟังก์ชัน $ on เพื่อที่จะเรียกขอบเขต $ $ ปิด (ประเภท, ฟังก์ชั่น) เราจะต้องมีการอ้างอิงถึงฟังก์ชั่นที่ไม่ระบุชื่อ เป็นเพียงการคิดในวิธีที่แตกต่างกับวิธีปกติจะเพิ่ม / ลบฟังเหตุการณ์ในภาษาเช่น ActionScript หรือรูปแบบการสังเกตใน Java
dannrob

60

เมื่อดูคำตอบส่วนใหญ่พวกเขาดูเหมือนจะซับซ้อนเกินไป เชิงมุมได้สร้างกลไกในการยกเลิกการลงทะเบียน

ใช้ฟังก์ชันการลงทะเบียนที่คืนค่าโดย$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();
});

ง่ายเหมือนสิ่งเหล่านี้มีคำตอบมากมาย แต่นี่กระชับกว่า
David Aguilar

8
เทคนิคที่ถูกต้อง แต่ bit ทำให้เข้าใจผิดเพราะไม่ได้ที่จะไม่ลงทะเบียนด้วยตนเองได้ที่$scope.$on เป็นตัวอย่างที่ดีกว่าจะใช้$destroy $rootScope.$on
hgoebl

2
คำตอบที่ดีที่สุด แต่ต้องการดูคำอธิบายเพิ่มเติมเกี่ยวกับสาเหตุที่เรียกผู้ฟังนั้นภายใน $ destroy ฆ่าผู้ฟัง
Mohammad Rafigh

1
@ MohammadRafigh การเรียกผู้ฟังที่อยู่ใน $ destroy เป็นจุดที่ฉันเลือกที่จะใส่ ถ้าฉันจำได้ถูกต้องนี่เป็นรหัสที่ฉันมีอยู่ในคำสั่งและมันก็สมเหตุสมผลว่าเมื่อขอบเขตของคำสั่งถูกทำลายผู้ฟังควรจะไม่ลงทะเบียน
long2know

@hgoebl ฉันไม่รู้ว่าคุณหมายถึงอะไร ตัวอย่างเช่นถ้าฉันมีคำสั่งที่ใช้ในหลาย ๆ ที่และแต่ละคนกำลังลงทะเบียนผู้ฟังสำหรับเหตุการณ์จะใช้ $ rootScope ได้อย่างไร $ on ช่วยฉันด้วย? การกำจัดขอบเขตของคำสั่งดูเหมือนจะเป็นสถานที่ที่ดีที่สุดในการกำจัดผู้ฟัง
long2know

26

รหัสนี้เหมาะกับฉัน:

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

1
การดูที่ $ rootScope $$ ผู้ฟังก็เป็นวิธีที่ดีในการสังเกตวงจรชีวิตของผู้ฟังและทำการทดลองด้วย
XML

ดูเรียบง่ายและยอดเยี่ยม ฉันคิดว่ามันเพิ่งลบการอ้างอิงของฟังก์ชั่น ไม่ใช่เหรอ
Jay Shukla

26
ไม่แนะนำวิธีนี้เนื่องจากสมาชิก $$ Listeners เป็นแบบส่วนตัว ในความเป็นจริงสมาชิกของวัตถุเชิงมุมที่มีคำนำหน้า '$$' เป็นส่วนตัวโดยการประชุม
shovavnik

5
ฉันไม่อยากจะแนะนำตัวเลือกนี้เพราะจะลบผู้ฟังทั้งหมดไม่ใช่เฉพาะคนที่คุณต้องการลบ มันอาจทำให้เกิดปัญหาในอนาคตเมื่อคุณเพิ่มผู้ฟังคนอื่นในส่วนอื่นของสคริปต์
Rainer Plumer

10

แก้ไข: วิธีที่ถูกต้องในการทำเช่นนี้คือในคำตอบของ @ LiviuT!

คุณสามารถขยายขอบเขตของ Angular ได้ตลอดเวลาเพื่อให้คุณสามารถลบผู้ฟังเช่นนี้ได้:

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

และนี่คือวิธีการใช้งาน:

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

และนี่คือเสียงพึมพำของมันในการดำเนินการ


ทำงานเหมือนจับใจ! แต่ฉันแก้ไขมันนิดหน่อยฉันนำมันไปไว้ในส่วนของ. run
aimiliano

รักทางออกนี้ ทำให้เป็นทางออกที่สะอาดกว่ามาก - อ่านง่ายขึ้นมาก +1
Rick

7

หลังจากตรวจแก้จุดบกพร่องรหัสฉันสร้างฟังก์ชั่นของตัวเองเช่นเดียวกับคำตอบ "blesh" นี่คือสิ่งที่ฉันทำ

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

ดังนั้นโดยการแนบฟังก์ชั่นของฉันไปที่ $ rootcope ตอนนี้มันสามารถใช้ได้กับคอนโทรลเลอร์ทั้งหมดของฉัน

และในรหัสของฉันฉันกำลังทำ

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

ขอบคุณ

แก้ไข: วิธี AngularJS การทำเช่นนี้อยู่ในคำตอบของ @ LiviuT! แต่ถ้าคุณต้องการที่จะยกเลิกการลงทะเบียนฟังในขอบเขตอื่นและในเวลาเดียวกันต้องการที่จะอยู่ห่างจากการสร้างตัวแปรท้องถิ่นเพื่อให้การอ้างอิงของฟังก์ชั่นการยกเลิกการลงทะเบียน นี่เป็นทางออกที่เป็นไปได้


1
ฉันลบคำตอบของฉันจริงๆเพราะคำตอบของ @ LiviuT นั้นถูกต้อง 100%
เบ็นเลช

คำตอบของ @blesh LiviuT นั้นถูกต้องและเป็นแนวทางที่ให้ในการยกเลิกการลงทะเบียน แต่ไม่ได้เชื่อมโยงกับสถานการณ์ที่คุณต้องยกเลิกการลงทะเบียนผู้ฟังในขอบเขตที่แตกต่างกัน ดังนั้นนี่เป็นทางเลือกที่ง่าย
Hitesh.Aneja

1
มันให้เบ็ดเดียวกันโซลูชั่นอื่นใดจะ คุณเพียงแค่ใส่ตัวแปรที่มีฟังก์ชั่นการทำลายในการปิดภายนอกหรือแม้กระทั่งในคอลเลกชันระดับโลก ... หรือทุกที่ที่คุณต้องการ
เบ็นเลช

ฉันไม่ต้องการสร้างตัวแปรทั่วโลกเพื่อเก็บการอ้างอิงของฟังก์ชันการยกเลิกการลงทะเบียนและฉันไม่เห็นปัญหาใด ๆ เกี่ยวกับการใช้ฟังก์ชัน $ off ของฉันเอง
Hitesh.Aneja

1

@ คำตอบของ LiviuT นั้นยอดเยี่ยม แต่ดูเหมือนว่าจะมีคนจำนวนมากสงสัยว่าจะสามารถเข้าถึงฟังก์ชั่นฉีกขาดของ handler ได้อีกครั้งจากขอบเขตหรือฟังก์ชัน $ $ อื่น ๆ หากคุณต้องการทำลายมันจากที่อื่น ๆ @ РустемМусабековคำตอบของใช้งานได้ดีมาก แต่ก็ไม่ได้เป็นไปในทางที่ผิด (และขึ้นอยู่กับสิ่งที่ควรจะเป็นรายละเอียดการใช้งานส่วนตัวซึ่งสามารถเปลี่ยนแปลงได้ตลอดเวลา) และจากที่นั่นมันก็ซับซ้อนมากขึ้น ...

ฉันคิดว่าคำตอบง่าย ๆ ที่นี่คือการอ้างอิงฟังก์ชันการดึงลง ( offCallMeFnในตัวอย่างของเขา) ในตัวจัดการเองและจากนั้นเรียกว่าขึ้นอยู่กับเงื่อนไขบางประการ อาจเป็นข้อโต้แย้งที่คุณรวมไว้ในเหตุการณ์ที่คุณออกอากาศหรือ $ emit ผู้จัดการสามารถฉีกตัวเองเมื่อใดก็ตามที่คุณต้องการทุกที่ที่คุณต้องการแบกเมล็ดพันธุ์แห่งการทำลายล้างของตัวเอง ชอบมาก

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

สองความคิด:

  1. นี่เป็นสูตรที่ยอดเยี่ยมสำหรับตัวจัดการแบบเรียกใช้ครั้งเดียว เพียงแค่วางเงื่อนไขและเรียกใช้selfDestructทันทีที่เสร็จสิ้นภารกิจฆ่าตัวตาย
  2. ฉันสงสัยว่าขอบเขตการกำเนิดจะถูกทำลายอย่างถูกต้องและเก็บขยะหรือไม่เนื่องจากคุณกำลังอ้างอิงกับตัวแปรที่ปิดอยู่ คุณต้องใช้ล้านสิ่งเหล่านี้เพื่อให้เป็นปัญหาความจำ แต่ฉันอยากรู้ หากใครมีข้อมูลเชิงลึกโปรดแบ่งปัน

1

ลงทะเบียนเบ็ดเพื่อยกเลิกการรับฟังผู้บรรยายเมื่อส่วนประกอบถูกลบ:

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

แม้ว่าจะไม่ใช่วิธีที่ยอมรับกันโดยทั่วไปในการทำเช่นนี้ แต่ก็มีบางครั้งที่วิธีนี้เป็นทางออกที่จำเป็น
Tony Brasunas

1

ในกรณีที่คุณต้องเปิดและปิดฟังหลาย ๆ ครั้งคุณสามารถสร้างฟังก์ชั่นที่มีbooleanพารามิเตอร์

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

0

'$ on' จะส่งคืนฟังก์ชันเพื่อยกเลิกการลงทะเบียน

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

คุณสามารถเรียกใช้ฟังก์ชัน unregister () เพื่อยกเลิกการลงทะเบียนฟัง


0

วิธีหนึ่งคือการทำลายผู้ฟังเมื่อคุณทำเสร็จแล้ว

var removeListener = $scope.$on('navBarRight-ready', function () {
        $rootScope.$broadcast('workerProfile-display', $scope.worker)
        removeListener(); //destroy the listener
    })
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.