简体   繁体   中英

AngularJS: Listen to events, one after the other

is there an angular-way to listen for events that occur one after the other? For example, I want to listen on a $rootScope for the $routeChangeSuccess and the $viewContentLoaded event. When the first event occurs, and after that, the second event occurs, I want to call a callback.

Is this possible? Or do I have to write it on my own? It would also be nice to configure if the order of the events is important or not. Or any ideas, how to implement such a behaviour?

Update

Because I haven't found anything on the web, I came up with my own solution. I think it works, but I don't know if there are any drawbacks with this method.

And any suggestions how to integrate this global into an AngularJS project? Or even as a bower component? Should I attach the function to a scope, or to the rootScope? Any help is appreciated!

Here is the Plunker link and the code: http://plnkr.co/edit/slfvUlFCh7fAlE4IPt8o?p=preview

var app = angular.module('plunker', []);

app.controller('MainCtrl', function($scope) {
  $scope.events = [];

  var successiveOn = function(events, eventScope, callback, orderImportant) {
    // array for the remove listener callback
    var removeListenerMethods = [];
    // events array that is passed to the callback method
    var eventsArr = [];
    // how many events are fired
    var eventCount = 0;
    // how many events should be fired
    var targetEventCount = events.length;
    // track the next event, only for orderImportant==true
    var nextEvent = events[0];
    // iterate over all event strings
    for (var i = 0; i < events.length; i++) {
      var event = events[i];
      // attach an $on listener, and store the remove listener function
      var removeListener = eventScope.$on(event, function(evt) {
        if (evt.name == nextEvent || !orderImportant) {
          ++eventCount;
          nextEvent = events[eventCount];
          eventsArr.push(evt);
          // if all events has fired, call the callback method and reset
          if (eventCount >= targetEventCount) {
            callback(eventsArr);
            nextEvent = events[0];
            eventCount = 0;
            eventsArr = [];
          }
        }
      });
      removeListenerMethods.push(removeListener);
    }

    // the return function is a anonymous function which calls all the removeListener methods
    return function() {
      for (var i = 0; i < removeListenerMethods.length; i++) {
        removeListenerMethods[i]();
      }
    }
  }

  // order is unimportant
  var removeListeners = successiveOn(["orderUnimportant1", "orderUnimportant2", "orderUnimportant3"], $scope, function(events) {
    var str = "Events in order of trigger: ";
    for (var i = 0; i < events.length; i++) {
      str += events[i].name + ", ";
    }
    $scope.events.push(str);
  }, false);

  $scope.$broadcast("orderUnimportant1");
  $scope.$broadcast("orderUnimportant2");
  $scope.$broadcast("orderUnimportant3"); // Events were triggered 1st time

  $scope.$broadcast("orderUnimportant3");
  $scope.$broadcast("orderUnimportant2");
  $scope.$broadcast("orderUnimportant1"); // Events were triggered 2nd time, order doesn't matter
  removeListeners();

  // order is important!
  var removeListeners = successiveOn(["OrderImportant1", "OrderImportant2", "OrderImportant3"], $scope, function(events) {
    var str = "Events in order of trigger: ";
    for (var i = 0; i < events.length; i++) {
      str += events[i].name + ", ";
    }
    $scope.events.push(str);
  }, true);

  $scope.$broadcast("OrderImportant1");
  $scope.$broadcast("OrderImportant2");
  $scope.$broadcast("OrderImportant3"); // Events were triggered

  $scope.$broadcast("OrderImportant1");
  $scope.$broadcast("OrderImportant3");
  $scope.$broadcast("OrderImportant2"); // Events were NOT triggered
  removeListeners();
});

Use the $q service aka the promise.

var routeChange = $q.defer();
var contentLoaded = $q.defer();

$rootScope.$on("$routeChangeSuccess", function() {
  routeChange.resolve();
});

$rootScope.$on("$viewContentLoaded", function() {
  contentLoaded.resolve();
});

$q.all([contentLoaded.promise, routeChange.promise]).then(function() {
  //Fire your callback here
});

Specific Order:

routeChange.then(function() {
  contentLoaded.then(function () {
     //Fire callback
  });
});

Without the nasty callback soup:

var routeChangeHandler = function() {
  return routeChange.promise;
}

var contentLoadedHandler = function() {
  return contentLoaded.promise
}

var callback = function() { 
  //Do cool stuff here
}

routeChangeHandler
  .then(contentLoadedHandler)
  .then(callback);

Thats pretty damn sexy...

More info on chained promises

I think Stens answer is the best way to tackle this if you want to do it with pure AngularJS facilities. However, often enough such cases indicate that you have to deal with more complex event stuf on a regular basis. If that's the case, I'd advice you to take a look at the Reactive Extensions and the rx.angular.js bridging library.

With the Reactive Extensions for JavaScript (RxJS) you can simplify the code to this:

Rx.Observable.combineLatest(
    $rootScope.$eventToObservable('$routeChangeSuccess'),
    $rootScope.$eventToObservable('$viewContentLoaded'),
    Rx.helpers.noop
)
.subscribe(function(){
    //do your stuff here
})

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM