I'm trying to setup solid error handling within my angular web application. I have a lot of various api calls using various sources, within many different controllers and directives. Ideally I'd like to handle resource errors in a manner like this:
$scope.loadData = function () {
$scope.loaded = false;
dataFactory.getData($scope.RecordId)
.$promise
.then(function (resp) {
$scope.Data = resp;
$scope.loaded = true;
}, function (resp) {
$scope.loaded = true;
$scope.handleResourceError(resp);
})
};
Since I inject $scope in all controllers/directives that would need this functionality, am I able to define a Resource Error method that would be accessible anywhere $scope is injected?
Interceptors might help you. instead of writing error handler function every time, you can just bind your function to root scope and call it form the "responseError" interceptor.
Here i bind a function to open the error model to $rootScope and i'm calling it form the interceptor. you can find the example below.
Sample code:
(function (global) {
"use strict";
angular.module("TestAPp").factory('httpInterceptor', ["$rootScope", "$q", function ($rootScope, $q) {
return {
responseError: function (response) {
/* Open Error model and display error */
$rootScope.openErrorModel(response);
/* reject deffered object so that it'll reach error block , else it'll execute success function */
return $q.reject(response);
}
};
}]);
}(this));
//registering interceprtor
(function (global) {
"use strict";
angular.module("TestApp", [])
.config([ '$httpProvider',function ($httpProvider) {
/* Register the interceptor */
$httpProvider.interceptors.push('httpInterceptor');
}]);
}(this));
PS: My openErrorModel definition
$rootScope.openErrorModel = function (error) {
$rootScope.errorMessage = error.data;
$('#errorModal').modal('show');
};
You can refer to Error Handling in angular for more info.
Related
Factory:
.factory("myFac", ['$http', '$q', function($http, $q) {
var defer = $q.defer();
$http.get('some/sample/url').then(function (response) { //success
/*
* Do something with response that needs to be complete before
* controller code is executed.
*/
defer.resolve('done');
}, function() { //error
defer.reject();
});
return defer.promise;
}]);
Controller:
.controller("testCtrl", ['$scope', 'myFac', function($scope, myFac) {
/*
* Factory code above is executed immediately as 'myFac' is loaded into controller.
* I do not want this.
*/
if($scope.someArbitraryBool === true) {
//THIS is when I want to execute code within myFac
myFac.then(function () {
//Execute code that is dependent on myFac resolving
});
}
}]);
Please let me know if it is possible to delay the code in the factory until I need it. Also, if there's a better way to execute on this concept, feel free to correct.
You factory has $http.get directly inside factory function which return custom $q promise. So while you inject the factory dependency inside your controller function, it ask angular to create an object of myFac factory function, while creating object of function it executes the code block which you have returned your factory, basically which returns promise.
What you could do is, just return a object {} from the factory function which will have method name with its definition, so when you inject inside angular controller it will return service object, which will various method like getData method. And whenever you want to call the method of factory you could do factoryName.methodName() like myFac.getData().
Also you have created a extra promise inside your service which is not needed in first place, as you can utilize the promise of $http.get (which returns a promise object.)
Factory
.factory("myFac", ['$http', function($http) {
var getData = return $http.get('some/sample/url').then(function (response) { //success
return 'done'; //return to resolve promise with data.
}, function(error) { //error
return 'error'; //return to reject promise with data.
});
return {
getData: getData
};
}]);
Controller
.controller("testCtrl", ['$scope', 'myFac', function($scope, myFac) {
if($scope.someArbitraryBool === true) {
//Here you could call the get data method.
myFac.getData().then(function () {
//Execute code that is dependent on myFac resolving
});
}
}]);
I am trying to inject the $location and AppConstant (factory) values inside of config block.
app.config(['RestangularProvider', function (RestangularProvider) {
RestangularProvider.setBaseUrl('/san/');
RestangularProvider.setErrorInterceptor(function (response, deferred, responseHandler) {
if (response.status > 400) {
//ERROR - can't inject
angular.injector().invoke(['AppConstant', '$location', function () {
AppConstant.redirectUrl = $location.path();
$location.path('/signin');
}]);
//
return false; // error handled
}
return true; // error not handled
});
}]);
But I am not injecting directly into config, I am trying to inject in middle of config on some function scope only.
Please help me to understand the angular and javascript.
According to the docs, it says that:
The ng module must be explicitly added.
You are using it like this: angular.injector().invoke(...
But you should be using it like this: angular.injector(['ng']).invoke(...
This is typical usage:
var $injector = angular.injector(['ng']);
$injector.invoke(function($rootScope, $compile, $document) {
$compile($document)($rootScope);
$rootScope.$digest();
});
Here are the docs
I would like to intercept all http error messages and give the user visual feedback. For this, I want to do something like this
.config(function($httpProvider) {
$httpProvider.interceptors.push(function($q) {
return {
'responseError': function(rejection) {
// show visual feedback or emit an event.
return $q.reject(rejection);
...
However, I can't inject $rootScope (to do a $rootScope.$emit()) since this is a config block. Nor is it possible to inject angular-growl or similar package, for the same reasons. How does one address this use case?
You can inject $rootScope together with $q:
.config(function($httpProvider) {
$httpProvider.interceptors.push(function($q, $rootScope) {
return {
'responseError': function(rejection) {
// show visual feedback or emit an event.
$rootScope.$emit( ... );
return $q.reject(rejection);
Try something like this
angular.module('interceptor.http', [])
.config(function ($httpProvider) {
$httpProvider.responseInterceptors.push('errorInterceptor');
})
.factory('errorInterceptor', function ($q, $rootScope) {
return function (promise) {
return promise.then(function (response) { },
function (response) {
$rootScope.$broadcast("error",{statusCode : 'error'});
return $q.reject(response);
});
};
});
... You can inject $rootScope. I don't know what you're talking about. However, if you'd like to inject dependencies at run-time that would otherwise require a service that would create a circular dependency, you can use the $injector service within config blocks like so:
// ... other angular code
.config(function($httpProvider){
$httpProvider.interceptors.push(function($q, $injector){
return {
responseError: function(arg1, arg2, argWhatever){
// This isn't run until there's actually an error (everything has already been configured)
// This is totally allowed
$http = $injector.get('$http');
// ... do whatever with your service
return $q.reject(/*something here*/);
}
};
});
});
I'm new to Angular and the general idea of promises so please bear with me on this one.
I have this general setup for a promise below:
ApiService.getDataUriForUrl(imageUrl).then(function(url){
requests--;
}, function(error){
console.log('oh no!');
});
I wrote this thinking that the function(error) would catch exceptions thrown on the getDataUriForUrl() call, but I now am led to believe it is for dealing with errors in the then(... area.
How should I be formatting this to intercept errors as I desire? (catching exceptions thrown in the getDataUriForUrl() call).
The errors thrown in the onFulfill callback will not be passed to the onReject callback.
Instead you can pass the first callback to then method and call the then method aging on the return.
ApiService.getDataUriForUrl(imageUrl).then(function(url){
requests--;
return new Error ();// you can't throw it
}).then(null, function(error){
console.log('oh no!');// I can catch error from the getData or the onFulfill callback
});
Instead of calling $q.reject or $q.resolve for every api method you can just write a interceptor for response and responseError. In which you can just $q.reject() or $q.resolve().
This would be cleaner and modularized.
Sample code:
(function (global) {
"use strict";
angular.module("TestAPp").factory('httpInterceptor', ["$rootScope", "$q", function ($rootScope, $q) {
return {
response:function(response){
return response;
},
responseError: function (response) {
return $q.reject(response);
}
};
}]);
}(this));
//registering interceprtor
(function (global) {
"use strict";
angular.module("TestApp", [])
.config([ '$httpProvider',function ($httpProvider) {
/* Register the interceptor */
$httpProvider.interceptors.push('httpInterceptor');
}]);
}(this));
You can refer to Error Handling in angular for more info.
I am trying to fire an event on $rootScope every time an ajax call is started.
var App = angular.module('MyApp');
App.config(function ($httpProvider) {
//add a transformRequest to preprocess request
$httpProvider.defaults.transformRequest.push(function () {
//resolving $rootScope manually since it's not possible to resolve instances in config blocks
var $rootScope = angular.injector(['ng']).get('$rootScope');
$rootScope.$broadcast('httpCallStarted');
var $log = angular.injector(['ng']).get('$log');
$log.log('httpCallStarted');
});
});
The event 'httpCallStarted' it's not being fired. I suspect that it's not correct to use $rootScope or any other instance service in config blocks. If so, how can I get an event everytime an http call is starting, without having to pass a config object in every time I am making a call?
Thanks in advance
You could always wrap $http in a service. Since services are only set up one time, you could just have the service factory set up the events for you. It feels a little hackish to me, honestly, but it's a good work around, since Angular doesn't have a global way to do this yet, unless something was added in 1.0.3 that I'm not aware of.
Here's a plunker of it working
And here's the code:
app.factory('httpPreConfig', ['$http', '$rootScope', function($http, $rootScope) {
$http.defaults.transformRequest.push(function (data) {
$rootScope.$broadcast('httpCallStarted');
return data;
});
$http.defaults.transformResponse.push(function(data){
$rootScope.$broadcast('httpCallStopped');
return data;
})
return $http;
}]);
app.controller('MainCtrl', function($scope, httpPreConfig) {
$scope.status = [];
$scope.$on('httpCallStarted', function(e) {
$scope.status.push('started');
});
$scope.$on('httpCallStopped', function(e) {
$scope.status.push('stopped');
});
$scope.sendGet = function (){
httpPreConfig.get('test.json');
};
});
Cagatay is right. Better use $http interceptors:
app.config(function ($httpProvider, $provide) {
$provide.factory('httpInterceptor', function ($q, $rootScope) {
return {
'request': function (config) {
// intercept and change config: e.g. change the URL
// config.url += '?nocache=' + (new Date()).getTime();
// broadcasting 'httpRequest' event
$rootScope.$broadcast('httpRequest', config);
return config || $q.when(config);
},
'response': function (response) {
// we can intercept and change response here...
// broadcasting 'httpResponse' event
$rootScope.$broadcast('httpResponse', response);
return response || $q.when(response);
},
'requestError': function (rejection) {
// broadcasting 'httpRequestError' event
$rootScope.$broadcast('httpRequestError', rejection);
return $q.reject(rejection);
},
'responseError': function (rejection) {
// broadcasting 'httpResponseError' event
$rootScope.$broadcast('httpResponseError', rejection);
return $q.reject(rejection);
}
};
});
$httpProvider.interceptors.push('httpInterceptor');
});
I think interceptors works for versions after 1.1.x.
There was responseInterceptors before that version.
I have verified that this code will work as you expect. As I mentioned above, you are not retrieving the injector that you think you are and need to retrieve the one being used for your app.
discussionApp.config(function($httpProvider) {
$httpProvider.defaults.transformRequest.push(function(data) {
var $injector, $log, $rootScope;
$injector = angular.element('#someid').injector();
$rootScope = $injector.get('$rootScope');
$rootScope.$broadcast('httpCallStarted');
$log = $injector.get('$log');
$log.log('httpCallStarted');
return data;
});
});
The best way to do this is to use an http interceptor. Check this link