I am trying to extend the $log service so that it also sends a message to a server. I am getting an error from Angular "Circular dependency found" as $http uses $log.
app.js:
angular.module("app").config(function($provide) {
$provide.decorator("$log", function($delegate, NodeLogger) {
var logFn = $delegate.log;
$delegate.log = function(message) {
NodeLogger.log(message);
logFn.apply(null, arguments);
};
});
});
NodeLogger.js:
angular.module("app").factory("NodeLogger", function($http) {
function log(type, message) {
var logMessage = type + ":" + message;
$http.post("http://localhost:3000/log", "message=" + logMessage);
}
return {
log: log
}
});
I have tried using $injector in app.js to load $http and just make the POST request, but it gives the same error. Is there a way to get around this? Can I avoid using $http / $resource?
Thanks!
This is a common issue of circular dependency when $http already injects $log and by decorating it with injecting your factory NodeLogger which inturn injects $http you are creating circular dependency. Instead of injecting your factory directly get the factory from injector, another way is to inject the $injector in your factory and get the $http instance from injector instead of injecting $http directly. This way you avoid circular dependency during factory creation. One important note is to return $delegate without which $log service will not hold any instance.
$provide.decorator("$log", function($delegate, $injector) {
var logFn = $delegate.log;
$delegate.log = function(message) {
//Get NodeLogger factory instance from injector
var NodeLogger = $injector.get('NodeLogger');
NodeLogger.log(message);
logFn.apply(null, arguments);
};
//Return the delegate
return $delegate;
});
angular.module("app", []).factory("NodeLogger", function($http) {
function log(type, message) {
var logMessage = type + ":" + message;
$http.post("http://localhost:3000/log", "message=" + logMessage);
}
return {
log: log
}
}).config(function($provide) {
$provide.decorator("$log", function($delegate, $injector) {
var logFn = $delegate.log;
$delegate.log = function(message) {
var NodeLogger = $injector.get('NodeLogger');
NodeLogger.log(message);
logFn.apply(null, arguments);
};
return $delegate;
});
}).run(function($log) {
$log.log("Hey");
});;
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
<div ng-app="app"></div>
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.
I have a code of
var viewerAngular = angular.module('ngAppDemo', ['restangular','btford.socket-io'])
.config(function(RestangularProvider) {
$.get('../config/config.xml',
function(data) {
$(data).find('contentserver').each(function() {
serverDetails.contentserver = assignServerDetails($(this));
var restprovider = RestangularProvider;
restprovider.setBaseUrl("http://"+serverDetails.contentserver.ip+":"+serverDetails.contentserver.port+"\:"+serverDetails.contentserver.port);
//$scope.init();
});
});
I need to invoke function init(), after reading the config(../config/config.xml) file.
I got an error of ReferenceError: $scope is not defined.
How can I add $scope in module.config? Or How can I call function from config?
If you need to add something to every scope in config, you can use $rootScope, but it's better practice to create a service for that data.
You can not ask for instance during configuration phase - you can ask only for providers.
var app = angular.module('app', []);
// configure stuff
app.config(function($routeProvider, $locationProvider) {
// you can inject any provider here
});
// run blocks
app.run(function($rootScope) {
// you can inject any instance here
});
See http://docs.angularjs.org/guide/module for more info.
var viewerAngular = angular.module('ngAppDemo', ['restangular','btford.socket-io'])
.config(function(RestangularProvider) {
$.get('../config/config.xml',
function(data) {
$(data).find('contentserver').each(function() {
serverDetails.contentserver = assignServerDetails($(this));
var restprovider = RestangularProvider;
restprovider.setBaseUrl("http://"+serverDetails.contentserver.ip+":"+serverDetails.contentserver.port+"\:"+serverDetails.contentserver.port);
var Scope=angular.element(document.querySelector('[ng-controller="controllerName"]')).scope();
Scope.init();
});
});
I want to inject a service into app.config, so that data can be retrieved before the controller is called. I tried it like this:
Service:
app.service('dbService', function() {
return {
getData: function($q, $http) {
var defer = $q.defer();
$http.get('db.php/score/getData').success(function(data) {
defer.resolve(data);
});
return defer.promise;
}
};
});
Config:
app.config(function ($routeProvider, dbService) {
$routeProvider
.when('/',
{
templateUrl: "partials/editor.html",
controller: "AppCtrl",
resolve: {
data: dbService.getData(),
}
})
});
But I get this error:
Error: Unknown provider: dbService from EditorApp
How to correct setup and inject this service?
Set up your service as a custom AngularJS Provider
Despite what the Accepted answer says, you actually CAN do what you were intending to do, but you need to set it up as a configurable provider, so that it's available as a service during the configuration phase.. First, change your Service to a provider as shown below. The key difference here is that after setting the value of defer, you set the defer.promise property to the promise object returned by $http.get:
Provider Service: (provider: service recipe)
app.provider('dbService', function dbServiceProvider() {
//the provider recipe for services require you specify a $get function
this.$get= ['dbhost',function dbServiceFactory(dbhost){
// return the factory as a provider
// that is available during the configuration phase
return new DbService(dbhost);
}]
});
function DbService(dbhost){
var status;
this.setUrl = function(url){
dbhost = url;
}
this.getData = function($http) {
return $http.get(dbhost+'db.php/score/getData')
.success(function(data){
// handle any special stuff here, I would suggest the following:
status = 'ok';
status.data = data;
})
.error(function(message){
status = 'error';
status.message = message;
})
.then(function(){
// now we return an object with data or information about error
// for special handling inside your application configuration
return status;
})
}
}
Now, you have a configurable custom Provider, you just need to inject it. Key difference here being the missing "Provider on your injectable".
config:
app.config(function ($routeProvider) {
$routeProvider
.when('/', {
templateUrl: "partials/editor.html",
controller: "AppCtrl",
resolve: {
dbData: function(DbService, $http) {
/*
*dbServiceProvider returns a dbService instance to your app whenever
* needed, and this instance is setup internally with a promise,
* so you don't need to worry about $q and all that
*/
return DbService('http://dbhost.com').getData();
}
}
})
});
use resolved data in your appCtrl
app.controller('appCtrl',function(dbData, DbService){
$scope.dbData = dbData;
// You can also create and use another instance of the dbService here...
// to do whatever you programmed it to do, by adding functions inside the
// constructor DbService(), the following assumes you added
// a rmUser(userObj) function in the factory
$scope.removeDbUser = function(user){
DbService.rmUser(user);
}
})
Possible Alternatives
The following alternative is a similar approach, but allows definition to occur within the .config, encapsulating the service to within the specific module in the context of your app. Choose the method that right for you. Also see below for notes on a 3rd alternative and helpful links to help you get the hang of all these things
app.config(function($routeProvider, $provide) {
$provide.service('dbService',function(){})
//set up your service inside the module's config.
$routeProvider
.when('/', {
templateUrl: "partials/editor.html",
controller: "AppCtrl",
resolve: {
data:
}
})
});
A few helpful Resources
John Lindquist has an excellent 5 minute explanation and demonstration of this at egghead.io, and it's one of the free lessons! I basically modified his demonstration by making it $http specific in the context of this request
View the AngularJS Developer guide on Providers
There is also an excellent explanation about factory/service/provider at clevertech.biz.
The provider gives you a bit more configuration over the .service method, which makes it better as an application level provider, but you could also encapsulate this within the config object itself by injecting $provide into config like so:
Alex provided the correct reason for not being able to do what you're trying to do, so +1. But you are encountering this issue because you're not quite using resolves how they're designed.
resolve takes either the string of a service or a function returning a value to be injected. Since you're doing the latter, you need to pass in an actual function:
resolve: {
data: function (dbService) {
return dbService.getData();
}
}
When the framework goes to resolve data, it will inject the dbService into the function so you can freely use it. You don't need to inject into the config block at all to accomplish this.
Bon appetit!
Short answer: you can't. AngularJS won't allow you to inject services into the config because it can't be sure they have been loaded correctly.
See this question and answer:
AngularJS dependency injection of value inside of module.config
A module is a collection of configuration and run blocks which get
applied to the application during the bootstrap process. In its
simplest form the module consist of collection of two kinds of blocks:
Configuration blocks - get executed during the provider registrations and configuration phase. Only providers and constants
can be injected into configuration blocks. This is to prevent
accidental instantiation of services before they have been fully
configured.
I don't think you're supposed to be able to do this, but I have successfully injected a service into a config block. (AngularJS v1.0.7)
angular.module('dogmaService', [])
.factory('dogmaCacheBuster', [
function() {
return function(path) {
return path + '?_=' + Date.now();
};
}
]);
angular.module('touch', [
'dogmaForm',
'dogmaValidate',
'dogmaPresentation',
'dogmaController',
'dogmaService',
])
.config([
'$routeProvider',
'dogmaCacheBusterProvider',
function($routeProvider, cacheBuster) {
var bust = cacheBuster.$get[0]();
$routeProvider
.when('/', {
templateUrl: bust('touch/customer'),
controller: 'CustomerCtrl'
})
.when('/screen2', {
templateUrl: bust('touch/screen2'),
controller: 'Screen2Ctrl'
})
.otherwise({
redirectTo: bust('/')
});
}
]);
angular.module('dogmaController', [])
.controller('CustomerCtrl', [
'$scope',
'$http',
'$location',
'dogmaCacheBuster',
function($scope, $http, $location, cacheBuster) {
$scope.submit = function() {
$.ajax({
url: cacheBuster('/customers'), //server script to process data
type: 'POST',
//Ajax events
// Form data
data: formData,
//Options to tell JQuery not to process data or worry about content-type
cache: false,
contentType: false,
processData: false,
success: function() {
$location
.path('/screen2');
$scope.$$phase || $scope.$apply();
}
});
};
}
]);
You can use $inject service to inject a service in you config
app.config(function($provide){
$provide.decorator("$exceptionHandler", function($delegate, $injector){
return function(exception, cause){
var $rootScope = $injector.get("$rootScope");
$rootScope.addError({message:"Exception", reason:exception});
$delegate(exception, cause);
};
});
});
Source: http://odetocode.com/blogs/scott/archive/2014/04/21/better-error-handling-in-angularjs.aspx
** Explicitly request services from other modules using angular.injector **
Just to elaborate on kim3er's answer, you can provide services, factories, etc without changing them to providers, as long as they are included in other modules...
However, I'm not sure if the *Provider (which is made internally by angular after it processes a service, or factory) will always be available (it may depend on what else loaded first), as angular lazily loads modules.
Note that if you want to re-inject the values that they should be treated as constants.
Here's a more explicit, and probably more reliable way to do it + a working plunker
var base = angular.module('myAppBaseModule', [])
base.factory('Foo', function() {
console.log("Foo");
var Foo = function(name) { this.name = name; };
Foo.prototype.hello = function() {
return "Hello from factory instance " + this.name;
}
return Foo;
})
base.service('serviceFoo', function() {
this.hello = function() {
return "Service says hello";
}
return this;
});
var app = angular.module('appModule', []);
app.config(function($provide) {
var base = angular.injector(['myAppBaseModule']);
$provide.constant('Foo', base.get('Foo'));
$provide.constant('serviceFoo', base.get('serviceFoo'));
});
app.controller('appCtrl', function($scope, Foo, serviceFoo) {
$scope.appHello = (new Foo("app")).hello();
$scope.serviceHello = serviceFoo.hello();
});
Using $injector to call service methods in config
I had a similar issue and resolved it by using the $injector service as shown above. I tried injecting the service directly but ended up with a circular dependency on $http. The service displays a modal with the error and I am using ui-bootstrap modal which also has a dependency on $https.
$httpProvider.interceptors.push(function($injector) {
return {
"responseError": function(response) {
console.log("Error Response status: " + response.status);
if (response.status === 0) {
var myService= $injector.get("myService");
myService.showError("An unexpected error occurred. Please refresh the page.")
}
}
}
A solution very easy to do it
Note : it's only for an asynchrone call, because service isn't initialized on config execution.
You can use run() method. Example :
Your service is called "MyService"
You want to use it for an asynchrone execution on a provider "MyProvider"
Your code :
(function () { //To isolate code TO NEVER HAVE A GLOBAL VARIABLE!
//Store your service into an internal variable
//It's an internal variable because you have wrapped this code with a (function () { --- })();
var theServiceToInject = null;
//Declare your application
var myApp = angular.module("MyApplication", []);
//Set configuration
myApp.config(['MyProvider', function (MyProvider) {
MyProvider.callMyMethod(function () {
theServiceToInject.methodOnService();
});
}]);
//When application is initialized inject your service
myApp.run(['MyService', function (MyService) {
theServiceToInject = MyService;
}]);
});
Well, I struggled a little with this one, but I actually did it.
I don't know if the answers are outdated because of some change in angular, but you can do it this way:
This is your service:
.factory('beerRetrievalService', function ($http, $q, $log) {
return {
getRandomBeer: function() {
var deferred = $q.defer();
var beer = {};
$http.post('beer-detail', {})
.then(function(response) {
beer.beerDetail = response.data;
},
function(err) {
$log.error('Error getting random beer', err);
deferred.reject({});
});
return deferred.promise;
}
};
});
And this is the config
.when('/beer-detail', {
templateUrl : '/beer-detail',
controller : 'productDetailController',
resolve: {
beer: function(beerRetrievalService) {
return beerRetrievalService.getRandomBeer();
}
}
})
Easiest way:
$injector = angular.element(document.body).injector()
Then use that to run invoke() or get()
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