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.