Search code examples
javascriptangularjshttp-getpushstatepopstate

Angular: $http.get() only fires every second onpopstate trigger


I have an AngularJS app that makes a call to an API and returns a bunch of data that users can then filter by tags for greater granularity in the results. Each time a tag is clicked to filter the data, the app makes a new $http.get() call, and the URL is modified with the appropriate query parameters so that the user can save the permalink and come back to any particular data set.

I'm trying to give the app proper history handling with window.history.pushState(), and passing the relevant query parameters for each history object as state data. I'm using window.onpopstate to detect when the back/forward buttons are clicked, and using that to make the new $http.get() call with the relevant state data from the history.

For some reason, the $http.get() function only fires on every second popstate, and then it makes two calls. It's almost as if there's some caching going on, but I haven't been able to find the culprit. This behaviour persists in both directions, backwards and forwards, and is consistently every second event. I've verified that window.history.length is only incremented by 1 for every tag added/removed, that the state data is being successfully sent, that new search queries are being correctly assembled, and that the request path is correct. It's just not firing. What's going on??

To illustrate, the behaviour flow looks like this:

  • Load page at /default
    • Add first tag: URL is /default&tags=a, $http.get() returns new data
    • Add second tag: URL is /default&tags=a,b, $http.get() returns new data
    • Add third tag: URL is /default&tags=a,b,c, $http.get() returns new data
    • Add fourth tag: URL is /default&tags=a,b,c,d, $http.get() returns new data
  • First back button event
    • window.onpopstate fires, URL is now /default&tags=a,b,c
    • No network changes
  • Second back button event
    • window.onpopstate fires, URL is now /default&tags=a,b
    • $http.get() fires, sends network request for data with /default&tags=a,b,c
    • $http.get() fires again, sends network request for data with /default&tags=a,b
    • dataset for /default&tags=a,b loads
  • Third back button event
    • window.onpopstate fires, URL is now /default&tags=a
    • No network changes
  • Fourth back button event
    • window.onpopstate fires, URL is now /default
    • $http.get() fires, sends network request for data with /default&tags=a
    • $http.get() fires again, sends network request for data with /default
    • dataset for /default loads

Relevant code snippet:

$scope.apiRequest = function(options, callback) {

    // Omitted: a bunch of functions to build query
    // based on user-selected tags.
    // I've verified that this is working correctly.

    $http.get(path)
      .then(function(response) {
        console.log('http request submitted');
        if (callback) {
          callback(response.data.response, response.data.count, response.data.facets);
          console.log('data returned');
        }
      }, function(response) {
          console.log('there has been an error');
     });
}

Neither the success nor error events fire. I've tried using $http.get().then().catch() to see if there might be something else going on, but for some reason I keep getting an error in my console that says that ...catch() is not a valid function, which is in and of itself bewildering. Any ideas?

Thanks!


Solution

  • This sounds indicative of a function not cycling through the $digest loop. In this case you may attempt to add $scope.$apply(); as the last line in your window.onpopstate handler function to kick the $digest cycle to execute your function call.

    This article, Notes On AngularJS Scope Life-Cycle helped me to better understand the $digest cycle and how you can force the $digest cycle to run with $scope.$apply(); Keep in mind you want to use $scope.$apply() sparingly but in some cases you are forced to kick off the cycle, especially with async callbacks.