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
Related
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.
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');
}]);
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
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);
};
}
I have been looking for a way to get services on initialization of my angular-js application, but could not find how to get it to work. In my case I want to get the $location service to observe the url.
Looking around, I found the services can be retrieved from the injector. To get the injector, I bootstrapped my application like this:
var angularApp = angular.module("MyApp", []);
var angularInjector = angular.injector(["MyApp", "ng"]);
angularApp.run(initializeAngularApp);
initializeAngularApp()
{
var location = angularInjector.get("$location");
}
This throws an Error:
Unknown provider: $rootElementProvider <- $rootElement <- $location
My understanding is that initializeAngularApp() should get called once the injector is done initializing. But judging from the error I get, it would not be the case.
What is the best way to get the services from the injector when my application initializes?
I found my answer and I did not need to instantiate the injector myself to get the service.
Services are injectable in the run() function, so doing:
angularApp.run(intializeAngularApp);
with
initializeAngularApp($rootScope, $location)
{
$rootScope.location = $location;
$rootScope.$watch("location.url()", function () { alert("url changed"); });
}
works.