Angular global error handler with service dependency - angularjs

Trying to create a global error handler that should present an error modal on error by configuring the $httpProvider adding a interceptor. The modal produces a dependency on a service. Which I can't inject into a config block.
I have tried lazy loading the service using $injector, but doesn't work.
How would you solve it?
edit just found $exceptionHandler, trying it. No luck cirk dep :$modal <- errorModalService <- $exceptionHandler <- $rootScope

Yes, it is true that AngularJS DI subsystem can be tricky with circular dependencies. Not sure what you've tried and what didn't work but you can always get a required dependency from the $injector. Doing so from a $http interceptor is pretty easy:
.factory('errInterceptor', function ($q, $injector) {
return {
responseError: function(response) {
$injector.get('$modal').open({
template: '<h4>$http error!</h4>',
});
}
}
})
Here is a working plunk: http://plnkr.co/edit/n172IrR9259qi4qG0H3I?p=preview

Related

Angular circular reference when using $http to log exceptions

I'm overriding the Angular $exceptionHandler to do some custom logging of exceptions. Unfortunately, my Logging service uses $http for logging, which is dependent on $exceptionHandler. Any thoughts on a pattern that would resolve my circular reference and still allow me to log via $http?
Here is my Service overriding $exceptionHandler:
angular.module('dashboard').factory('$exceptionHandler', ['$log', 'Logging',
function($log, Logging){
return function globalErrorHandler(exception, cause){
var itemToLog = new logItem('dashboard', 'General Error', exception.message + ": " + exception.stack);
Logging.logEvent(itemToLog);
$log.warn(exception, cause);
}
}]);
and my custom Logging service:
angular.module('dashboard').factory('Logging', ['$http', function($http) {
return {
logEvent: function(item){
$http.post('/api/loggingservice/event', item)
.success(function(data){
return data;
}).
error(function(data){
console.log('error logging event: '+JSON.stringify(data));
});;
}
};
}]);
And the error message I receive is:
angular.min.js:6 Uncaught Error: [$injector:cdep]
https://docs.angularjs.org/error/$injector/cdep?p0=$http%20%3C-%20Logging%20%3C-%20$exceptionHandler%20%3C-%20$rootScope%20%3C-%20$http%20%3C-%20UserManagement%20%3C-%20Menus
$exceptionHandler is used by other core services that $http depends on. This makes injecting $http or a service that depends on $http impossible, because this results in circular dependency.
A usual recipe to avoid CD in Angular 1 is using $injector.get(...) instead of injecting a service in service factory/constructor function. However, the developer should be aware why it is done and what it is going on there.
Doing something like
function($log, $injector){
var Logging = $injector.get('Logging');
return function globalErrorHandler(exception, cause){ ... }
}
won't break circular dependency, because $exceptionHandler is eagerly instantiated by core services which $http depends on, which Logging depends on.
On the other hand,
function($log, $injector){
return function globalErrorHandler(exception, cause){
var Logging = $injector.get('Logging');
...
}
}
will work because this way Logging is lazily instantiated. This will result in executing $injector.get(...) on each handler call. But this is fine, since $injector.get(...) has no performance impact and can be called multiple times, especially in non-critical places.

$httpProvider interceptor not working when $http is injected into a factory

I have a service called 'api', registered something like this:
angular
.module('myApp')
.factory('api', ['$http', function ($http) {
// do stuff with $http.get() etc here.
}]);
... and $http is being customized like this elsewhere:
angular
.module('myApp')
.factory('httpInterceptor', ['$rootScope', function ($rootScope) {
// do stuff to intercept http requests and auth things here
}]);
angular
.module('myApp')
.config(function ($httpProvider) {
$httpProvider.interceptors.push('httpInterceptor');
});
The interceptors are working when I directly inject $http into a controller, but when I use the api service in my controller, the $http customizations are not working. Looks like Angular does not add the intercepts by the point it's required by the factory I created.
How do I solve this?
The question was based on a wrong premise. The interceptors are in fact working, I was just using the wrong hook point. Instead of customizing the responseError property of the interceptor object, I was trying to intercept errors from the response property itself. This is why I thought the interceptors weren't working at all.
This problem does not really exist. The provider's interceptors are working correctly even in a factory.
You can try to make the httpInterceptor a variable inside the config instead of making it a factory:
angular
.module('myApp')
.config(function ($httpProvider) {
var httpInterceptor = ['$rootScope',
function($rootScope) {
// do stuff to intercept http requests and auth things here
}
];
$httpProvider.responseInterceptors.push(httpInterceptor);
});
Please note that I've also changed $httpProvider.interceptors to $httpProvider.responseInterceptors and the parameter passed to push() is not a string.
Let me know if that helps.
EDIT:
You can also consider using this plugin: https://github.com/witoldsz/angular-http-auth. It has this cool feature after the interception: "The authService will then retry all the requests previously failed due to HTTP 401 response."

How do I call a Service from my $httpProvider

I've been struggling with this all morning. I'm trying to call a Service from within my config($httpprovider). I've read a lot of people explaining that Services are not yet available during config. I get that, but my service should only be called from within the interceptor, which is at runtime.
I found a semi-solution that manually injected the Service manually like below, but it's not working for me, since it seems a completely new instance of the Service is being created, and I want to keep using the same instance throughout the app (since it stores a messageQueue array). Any suggestions?
.config(['$httpProvider', function ($httpProvider) {
$httpProvider.interceptors.push(function ($q) {
return {
'responseError': function (rejection) {
//responses 300 and up are errors
//Manually inject service from the myServices module, since it is not yet known when .config is called
var $injector = window.angular.injector(['myServices']);
var MessageService = $injector.get('MessageService');
MessageService.setMessage("We were unable to load all necessary data. We're terribly sorry! Please try reloading this page or contact us if the problem isn't solved.");
}
};
});
}])
You can register your interceptor as a service using a factory, according to angularjs 1.2.2 documentation, so it should be provided with the right dependencies, using the following syntax :
.config(['$httpProvider', function ($httpProvider) {
$provide.factory('myHttpInterceptor', ['MessageService', function (MessageService) {
return {
'responseError': function (rejection) {
//responses 300 and up are errors
MessageService.setMessage("We were unable to load all necessary data. We're terribly sorry! Please try reloading this page or contact us if the problem isn't solved.");
}
};
}]);
$httpProvider.interceptors.push('myHttpInterceptor');
}]);

Injecting $http into angular factory($exceptionHandler) results in a Circular dependency

When I try inject $http into an overridden factory I get the error:
Uncaught Error: [$injector:cdep] Circular dependency found: $http <-
$exceptionHandler <- $rootScope
AngularModule.factory('$exceptionHandler', function ($http) {
any ideas how to resolve? if I inject using [], $http is undefined
edit__________________
as per an answer below I tried:
MyModule.config(function($provide, $http) {
$provide.decorator("$exceptionHandler", function($delegate) {
return function(exception, cause) {..
but I still get the circular error:
Uncaught Error: [$injector:cdep] Circular dependency found: $http <-
$exceptionHandler <- $rootScope
Inject the $injector and then get the $http service from there. Something like this:
AngularModule.factory('$exceptionHandler', function ($injector) {
var $http = $injector.get("$http");
See https://groups.google.com/forum/#!topic/angular/lbFY_14ZtnU/discussion
However, this will completely override the $exceptionHandler functionality provided by Angular. If you just want to add the server-side log to the existing functionality, see this question about augmenting $exceptionHandler functionality.
I'm using this solution, because of circular dependency issues with rootScope:
angular
.module('facilityLog')
.provider('$exceptionHandler', function() {
"use strict";
this.$get = function($injector) {
function exceptionHandler(exception, cause) {
// This is the part where you get the instance of $http in your case
var $rootScope = $injector.get('$rootScope');
//...
}
return exceptionHandler;
}});
So if you request the instance inside the exceptionHandler-Function you will not get the circular dependency-error.
I used the following to solve this. Note how the array notation is used to make this minification safe.
Note also, that I am completely overriding the $esceptionHandler and using my own service to replace it.
angular
.module('app')
.factory('$exceptionHandler', $exceptionHandler);
$exceptionHandler.$inject = ['$injector', 'exceptionLoggingService'];
function $exceptionHandler($injector, exceptionLoggingService)
{
return function(exception, cause)
{
exceptionLoggingService.http = exceptionLoggingService.http || $injector.get('$http');
exceptionLoggingService.error(exception, cause);
};
}

AngularJS Logging to Server

New to AngularJS but slowly getting going with it and so far I like it. I'm coming from the Java/JSP world so bit of a learning curve!
Anyway, I'm trying to figure out how to send all logging to a server side service.
In my app module config I've overridden the Log implementation and I have this working fine - I have log statements automatically creating simply alerts.
Next step was to send them to the server. I've create a service for this using $resource. I then try to autowire the service into my app module config and this is where I've problems.
It's giving me a circular dependency error which I'm not sure what it means or how to resolve it.
Anyone done anything similar before who may have encountered this problem?
The error I'm seeing is 'Uncaught Error: Circular dependency: $browser <- $httpBackend <- $http <- $resource <- LoggingService <- $log <- $exceptionHandler <- $rootScope'
My app config is:
app.config(['$provide', function($provide) {
$provide.decorator('$log', function($delegate, LoggingService) {
var _info = $delegate.info;
var _error = $delegate.error;
$delegate.info = function(msg){
_info(msg);
};
$delegate.error = function(msg){
_error(msg);
//log.error(msg);
alert('Error:' + msg);
};
return $delegate;
});
}]);
Just trying to pass in my LoggingService results in the error.
My logging service is very simple:
app.factory('LoggingService', ['$resource', function($resource) {
return $resource('http://localhost:port/myservice/logging/', {port: ':8080'},
{
save: {method: 'POST'}
}
);
}]);
Regards,
Kevin.
As per Injecting Dependencies in config() modules - AngularJS
You can only use providers in .config
See registering a service with $provide
https://docs.angularjs.org/guide/services

Resources