Question

I can understand how to make $http request interceptor with retry logic like in this question

But I cannot figure out how to make a modal popup(angular-ui - bootstrap) with retry button which get poped up every time error happens, even repeatedly. Can anyone drop me some advise?

Was it helpful?

Solution

There are a number of components to this, and you need to know about chaining promises to understand how it all fits together. First the interceptor

app.factory('RetryInterceptor', function($injector, $timeout, $q) {
  return {
   'responseError': function(rejection) {
      // Avoid circular dependency issues
      var Retry = $injector.get('Retry');
      var $http = $injector.get('$http');

      // Timeout is just to keep UI from changing too quickly
      return $timeout(angular.noop,1000).then(function() {
        return Retry.show();
      }).then(function() {
        return $http(rejection.config);
      }, function() {
        return $q.reject(rejection);
      });
    }
  }
});

registered as

app.config(function($httpProvider) {
  $httpProvider.interceptors.push('RetryInterceptor');
});

The above responseError interceptor returns a promise that is resolved/rejected with the returned promise of Retry.open. If that promise is resolved, then it will retry the original $http request. If it's rejected, then nothing will happen, other than the promise returned from the original call to $http will be rejected, with the same rejection object it would have had without the dialog being shown.

Retry is a custom service, that exposes a single method, open. You could have this in the interceptor, but this keeps things fairly modular:

app.service('Retry', function Retry($window, $modal) {
  this.show  = function() {
    return $modal.open({
      templateUrl: 'retry-dialog.html',
      controller: 'RetryController'
    }).result;
  }
});

The show method returns a promise from the $modal service. This promise is controlled by the $modalInstance object passed to the controller:

app.controller('RetryController', function($scope, $modalInstance) {
  $scope.retry = function() {
    // Will resolve the promise
    $modalInstance.close();
  };
  $scope.cancel = function() {
    // Will reject the promise
    $modalInstance.dismiss();
  }
});

And the template for the modal can be something like:

<div class="modal-header">
  <h3>Error!</h3>
</div>
<div class="modal-body">
  <p>Something went wrong!</p>
</div>
<div class="modal-footer">
  <button class="btn btn-primary" ng-click="retry()">Retry</button>
  <button class="btn btn-danger" ng-click="cancel()">Cancel</button>
</div>

which will then resolve the promise if "Retry" is clicked, or reject the promise if "Cancel" is clicked.

You can see all this working in this Plunker.

A few extra points:

  • You could pass details of the original failure using the resolve option to $modal.open, so you can customise the message shown.

  • You might not want to show the dialog for all requests. If there is an error fetching the template for the modal, for example, you'll end up with an infinite loop. You could test for the http status code to only show the dialog for certain failures, or another way of controlling this is to add custom options to the config object you pass to $http, which you can then test for in the responseError interceptor, to determine how the failure is handled.

OTHER TIPS

Look at this Angular module http://ngmodules.org/modules/http-auth-interceptor It is made to intercept non-authorized requests and retry them. You can easily adapt this module for your needs. Replace if (rejection.status === 401 && !rejection.config.ignoreAuthModule) with your logic. Bind to 'event:auth-loginConfirmed' event with $rootScope.$on('event:auth-loginConfirmed', ...) at app.run() to open your modal. In your modal inject authService factory and make a call to authService.loginConfirmed() this will retry request with your error logic

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top