Làm thế nào để hủy đăng ký một sự kiện phát sóng trong angularJS. Cách xóa chức năng đã đăng ký qua $ trên


278

Tôi đã đăng ký người nghe của mình cho một sự kiện phát sóng $ bằng cách sử dụng chức năng $ on

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

và tôi muốn hủy đăng ký người nghe này dựa trên một quy tắc kinh doanh cụ thể. Nhưng vấn đề của tôi là một khi nó đã được đăng ký, tôi không thể hủy đăng ký nó.

Có phương pháp nào trong AngularJS để bỏ đăng ký một người nghe cụ thể không? Một phương thức như $ trên mà không đăng ký sự kiện này, có thể là $ off. Vì vậy, dựa trên logic kinh doanh tôi có thể nói

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

và chức năng này ngừng được gọi khi ai đó phát sự kiện "onViewUpdated".

Cảm ơn

EDIT : Tôi muốn hủy đăng ký người nghe từ một chức năng khác. Không phải chức năng nơi tôi đăng ký nó.


2
Đối với bất cứ ai thắc mắc, chức năng trả về được ghi lại ở đây
Fagner Brack

Câu trả lời:


477

Bạn cần lưu trữ hàm trả về và gọi nó để hủy đăng ký khỏi sự kiện.

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

Điều này được tìm thấy trong mã nguồn :) ít nhất là trong 1.0.4. Tôi sẽ chỉ đăng mã đầy đủ vì nó ngắn

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

Ngoài ra, xem các tài liệu .


Đúng. Sau khi gỡ lỗi mã sorce tôi phát hiện ra rằng có một mảng người nghe $$ có tất cả các sự kiện và tạo ra hàm $ off của tôi. Cảm ơn
Hitesh.Aneja

Trường hợp sử dụng thực tế mà bạn không thể sử dụng theo cách hủy đăng ký góc được cung cấp là gì? Là việc hủy đăng ký được thực hiện trong một phạm vi khác không được liên kết với phạm vi đã tạo ra người nghe?
Liviu T.

1
Vâng, tôi thực sự đã xóa câu trả lời của mình vì tôi không muốn làm mọi người bối rối. Đây là cách thích hợp để làm điều này.
Ben Lesh

3
@Liviu: Điều đó sẽ trở thành vấn đề đau đầu với ứng dụng đang phát triển. Không chỉ sự kiện này còn có rất nhiều sự kiện khác và không nhất thiết là tôi luôn luôn đăng ký trong cùng một chức năng phạm vi. Có thể có trường hợp khi tôi gọi một chức năng đang đăng ký người nghe này nhưng lại hủy đăng ký người nghe trên cuộc gọi khác, ngay cả những trường hợp đó tôi sẽ không nhận được tài liệu tham khảo trừ khi tôi lưu trữ chúng ngoài phạm vi của mình. Vì vậy, đối với việc thực hiện hiện tại, việc triển khai của tôi có vẻ là giải pháp khả thi đối với tôi. Nhưng chắc chắn muốn biết lý do tại sao AngularJS đã làm điều đó theo cách này.
Hitesh.Aneja

2
Tôi nghĩ rằng Angular đã làm theo cách này bởi vì rất nhiều hàm ẩn danh nội tuyến được sử dụng làm đối số cho hàm $ on. Để gọi $ scope. $ Off (loại, hàm), chúng ta cần giữ một tham chiếu đến hàm ẩn danh. Nó chỉ suy nghĩ theo một cách khác với cách người ta thường làm thêm / xóa người nghe sự kiện bằng một ngôn ngữ như ActionScript hoặc mẫu có thể quan sát được trong Java
dannrob

60

Nhìn vào hầu hết các câu trả lời, chúng có vẻ quá phức tạp. Angular đã được xây dựng trong các cơ chế để hủy đăng ký.

Sử dụng chức năng hủy đăng ký được trả về bởi$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();
});

Đơn giản như vậy, có rất nhiều câu trả lời nhưng điều này ngắn gọn hơn.
David Aguilar

8
Về mặt kỹ thuật, mặc dù có một chút sai lệch, bởi vì $scope.$onkhông phải đăng ký thủ công $destroy. Một ví dụ tốt hơn sẽ là sử dụng a $rootScope.$on.
hgoebl

2
câu trả lời hay nhất nhưng muốn xem giải thích thêm về lý do tại sao việc gọi người nghe đó bên trong $ kill sẽ giết chết người nghe.
Mohammad Rafigh

1
@MohammadRafigh Gọi người nghe bên trong $ kill chỉ là nơi tôi chọn đặt nó. Nếu tôi nhớ lại một cách chính xác, đây là mã mà tôi có trong một chỉ thị và nó có ý nghĩa rằng khi phạm vi chỉ thị bị phá hủy, người nghe sẽ không được đăng ký.
long2ledge

@hgoebl Tôi không hiểu ý bạn. Ví dụ, nếu tôi có một lệnh được sử dụng ở nhiều nơi và mỗi lệnh đang đăng ký một người nghe cho một sự kiện, làm thế nào để sử dụng $ rootScope. $ Giúp tôi với? Xử lý phạm vi của chỉ thị dường như là nơi tốt nhất để xử lý người nghe.
long2ledge

26

Mã này hoạt động với tôi:

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

1
Nhìn vào $ rootScope. Người nghe $$ cũng là một cách tốt để quan sát vòng đời của người nghe và thử nghiệm nó.
XML

Trông đơn giản và tuyệt vời. Tôi nghĩ rằng nó chỉ cần loại bỏ tham chiếu của chức năng. phải không
Jay Shukla

26
Giải pháp này không được khuyến khích vì thành viên người nghe $$ được coi là riêng tư. Trong thực tế, bất kỳ thành viên nào của một đối tượng góc có tiền tố '$$' là riêng tư theo quy ước.
shovavnik

5
Tôi không khuyến nghị tùy chọn này, vì nó loại bỏ tất cả người nghe, không chỉ người bạn cần xóa. Nó có thể gây ra vấn đề trong tương lai khi bạn thêm một người nghe khác trong một phần khác của kịch bản.
Rainer Pl Count

10

EDIT: Cách chính xác để làm điều này là trong câu trả lời của @ LiviuT!

Bạn luôn có thể mở rộng phạm vi của Angular để cho phép bạn loại bỏ những người nghe như vậy:

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

Và đây là cách nó hoạt động:

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

Và đây là một plunker của nó trong hành động


Làm việc như người ở! nhưng tôi đã chỉnh sửa nó một chút, tôi đặt nó vào phần
.run

Yêu giải pháp này. Làm cho một giải pháp sạch hơn nhiều - dễ đọc hơn nhiều. +1
Rick

7

Sau khi gỡ lỗi mã, tôi đã tạo ra chức năng của riêng mình giống như câu trả lời của "b Meat". Vì vậy, đây là những gì tôi đã làm

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

vì vậy bằng cách đính kèm chức năng của tôi vào $ rootscope bây giờ nó có sẵn cho tất cả các bộ điều khiển của tôi.

và trong mã của tôi, tôi đang làm

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

Cảm ơn

EDIT: Cách AngularJS để làm điều này là trong câu trả lời của @ LiviuT! Nhưng nếu bạn muốn hủy đăng ký người nghe trong một phạm vi khác và đồng thời muốn tránh xa việc tạo các biến cục bộ để giữ các tham chiếu của chức năng hủy đăng ký. Đây là một giải pháp có thể.


1
Tôi thực sự đang xóa câu trả lời của mình, vì câu trả lời của @ LiviuT là chính xác 100%.
Ben Lesh

Câu trả lời của @bledh LiviuT là chính xác và theo cách tiếp cận được cung cấp để loại bỏ đăng ký nhưng không phù hợp với các tình huống mà bạn phải đăng ký lại người nghe trong phạm vi khác nhau. Vì vậy, đây là một thay thế dễ dàng.
Hitesh.Aneja

1
Nó cung cấp cùng một hook lên bất kỳ giải pháp nào khác. Bạn chỉ cần đặt biến chứa hàm hủy trong bao đóng bên ngoài hoặc thậm chí trong bộ sưu tập toàn cầu ... hoặc bất cứ nơi nào bạn muốn.
Ben Lesh

Tôi không muốn tiếp tục tạo các biến toàn cục để giữ các tham chiếu về các chức năng hủy đăng ký và tôi cũng không thấy bất kỳ vấn đề nào khi sử dụng hàm $ off của riêng tôi.
Hitesh.Aneja

1

Câu trả lời của @ LiviuT là tuyệt vời, nhưng dường như khiến nhiều người thắc mắc làm thế nào để truy cập lại chức năng xé của trình xử lý từ một phạm vi hoặc hàm $ khác, nếu bạn muốn phá hủy nó từ một nơi khác ngoài nơi nó được tạo. @ Câu trả lời của Мусабеков chỉ hoạt động tuyệt vời, nhưng không phải là rất thành ngữ. (Và dựa vào những gì được coi là một chi tiết triển khai riêng tư, có thể thay đổi bất cứ lúc nào.) Và từ đó, nó trở nên phức tạp hơn ...

Tôi nghĩ rằng câu trả lời dễ dàng ở đây chỉ đơn giản là mang một tham chiếu đến hàm xé ( offCallMeFntrong ví dụ của anh ta) trong chính trình xử lý, và sau đó gọi nó dựa trên một số điều kiện; có lẽ là một đối số mà bạn đưa vào sự kiện bạn $ phát sóng hoặc $ phát ra. Người xử lý có thể tự xé nát mình, bất cứ khi nào bạn muốn, bất cứ nơi nào bạn muốn, mang theo những hạt giống hủy diệt của chính họ. Thích như vậy:

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

Hai suy nghĩ:

  1. Đây là một công thức tuyệt vời cho một trình xử lý một lần chạy. Chỉ cần bỏ các điều kiện và chạy selfDestructngay khi nó đã hoàn thành nhiệm vụ tự sát.
  2. Tôi tự hỏi về việc liệu phạm vi khởi tạo sẽ bị phá hủy và thu gom rác đúng cách hay không, cho rằng bạn đang mang các tham chiếu đến các biến được đóng. Bạn sẽ phải sử dụng một triệu trong số này để thậm chí có vấn đề về bộ nhớ, nhưng tôi tò mò. Nếu ai có cái nhìn sâu sắc, xin vui lòng chia sẻ.

1

Đăng ký một hook để hủy đăng ký người nghe của bạn khi loại bỏ thành phần:

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

Mặc dù không phải là cách được chấp nhận chung để làm điều này, nhưng có những lúc đây là giải pháp cần thiết.
Tony Brasunas

1

Trong trường hợp bạn cần bật và tắt trình nghe nhiều lần, bạn có thể tạo một hàm với booleantham số

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

0

'$ on' chính nó trả về chức năng cho người đăng ký

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

bạn có thể gọi hàm unregister () để hủy đăng ký người nghe đó


0

Một cách đơn giản là tiêu diệt người nghe một khi bạn đã hoàn thành nó.

var removeListener = $scope.$on('navBarRight-ready', function () {
        $rootScope.$broadcast('workerProfile-display', $scope.worker)
        removeListener(); //destroy the listener
    })
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.