I want to post errors that happen inside an angular application.
I followed the approach given in this related question, as suggested in the answer, I injected the $injector and then got the $http service from there.
But the line
Uncaught Error: Circular dependency: $http <- $exceptionHandler <- $rootScope
keeps comming.
here is the fiddle with the problem
with the relevant code:
var mod = angular.module('test', []);
mod.config(function ($provide) {
$provide.decorator("$exceptionHandler", ['$delegate', '$injector', function ($delegate, $injector) {
var $http = $injector.get("$http");
}]);
});
mod.controller('testCtrl', function ($scope) {
});
If you comment the line
var $http = $injector.get("$http");
The circular dependency error is gone.
I think I'm missing something in my understanding. What am I doing wrong? After all, that seems to have worked for others.
Any suggestion on how to achieve my initial goal of 'posting errors to a service' is also welcomed.
Thanks everyone
wrap your injector code within function
var mod = angular.module('test', []);
mod.config(function ($provide) {
$provide.decorator("$exceptionHandler", ['$delegate', '$injector',function ($delegate, $injector) {
return function (exception, cause) {
var $http = $injector.get("$http");
}
}]);
});
mod.controller('testCtrl', function ($scope) {
});
You can do like this.
This will break your circular dependency error and you can achieve your goal too.
If I am not wrong then you just wanna call web service in exception Handler.
var httpCallOnAngularError = angular.module('exceptionTestApp', []);
/*httpCallOnAngularError.factory('$exceptionHandler', function () {
return function (exception, cause) {
alert(exception.message);
};
});*/
httpCallOnAngularError.config(function ($provide) {
$provide.decorator("$exceptionHandler", function ($delegate, $injector) {
return function (exception, cause) {
var $rootScope = $injector.get('$rootScope');
$delegate(exception, cause);
$rootScope.logAngularError();
alert(exception.message);
};
});
});
httpCallOnAngularError.run(function ($http, $rootScope) {
$rootScope.logAngularError = function () {
//Call your webservice here.
$http.get("http://jsonplaceholder.typicode.com/posts/1")
.success(function (response) {
console.log(JSON.stringify(response));
});
};
});
httpCallOnAngularError.controller('exceptionTestCtrl', function ($scope) {
throw {message: 'Log this error by calling webservice.'};
});
You can verify this by this template.
<div ng-app="exceptionTestApp" ng-controller="exceptionTestCtrl">
</div>
Webservice call in Exception Handler $http in $exceptionHandler
Related
I want to inject Service in a controller. The Service will return $http.get() method.
Error : [$injector:unpr] http://errors.angularjs.org/1.6.4/$injector/unpr?p0=JsonFilterProvider%20%3C-%20JsonFilter
Please suggest whats wrong in my code?
<script>
var app = angular.module("myApp", []);
app.controller("myCntlr", ['$scope', 'myhttpService', function ($scope, myhttpService) {
$scope.myHttpMessage = myhttpService.httpGetService();
}]);
app.service("myhttpService", ['$http', '$scope', function ($http, $scope) {
this.httpGetService = function () {
console.log("httGetService");
$http.get('https://reqres.in/api/users').then(function (successResponse) {
console.log("http Get");
return successResponse;
}, function (errorResponse) {
console.log("http Get Error");
return errorResponse
});
};
}]);
</script>
<div ng-app="myApp" ng-controller="myCntlr">
<p>Http Message:{{myHttpMessage|Json}}</p>
</div>
You can not inject $scope in service. that's not allowed. Instead, you can return promise from service and process inside controller something like this.
app.service("myhttpService",['$http', function ($http) {
this.httpGetService = function () {
return $http.get('https://reqres.in/api/users');
}
}]);
app.controller("myCntlr", ['$scope', 'myhttpService', function ($scope, myhttpService) {
myhttpService.httpGetService().then(function(response){
$scope.myHttpMessage = response.data;
}, function(error){
//do something on failure
});
}]);
The actual issue is you are not getting the response from your service. So the json filter throws an error
<p>Http Message:{{myHttpMessage | json}}</p>
Make sure yo return the result back from the service with a return command.
return $http.get('https://reqres.in/api/users').then(function (successResponse)
I cannot get the test result to pass I'm using a very basic implementation to understand testing deeper.
I have a factory which returns a promise, accessed from my controller. I want to test that the call succeeds and assigns the response to the repos var. Following is the code:
'use strict';
angular.module('app')
.factory('searchServ', function ($timeout, $q, $http) {
return {
fetch: function(user) {
var deferred = $q.defer();
$timeout(function(){
$http({method: 'GET', url: 'https://api.github.com/users/' + user + '/repos'}).then(function(repos) {
deferred.resolve(repos.data);
}, function(reason){
deferred.reject(reason.status);
console.log(reason);
});
}, 30);
return deferred.promise;
}
};
})
.controller('MainCtrl', function ($scope, searchServ) {
$scope.results = function(user) {
$scope.message = '';
searchServ.fetch(user).then(function (repos) {
if(repos.length){
$scope.message = '';
$scope.repos = repos;
}
else{
$scope.message = 'not found'
}
}, function (){
$scope.message = 'not found';
});
};
});
//Test
'use strict';
describe('MainCtrl', function () {
var scope, searchServ, controller, deferred, repos = [{name: 'test'}];
// load the controller's module
beforeEach(module('app'));
beforeEach(inject(function($controller, $rootScope, $q) {
searchServ = {
fetch: function () {
deferred = $q.defer();
return deferred.promise;
}
};
spyOn(searchServ, 'fetch').andCallThrough();
scope = $rootScope.$new();
controller = $controller('MainCtrl', {
$scope: scope,
fetchGithub: fetchGithub
});
}));
it('should test', function () {
expect(scope.test).toEqual('ha');
});
it('should bind to scope', function () {
scope.results();
scope.$digest();
expect(scope.message).toEqual('');
//expect(scope.repos).not.toBe(undefined);
});
});
Running the test gives me the following error :
TypeError: undefined is not a function (evaluating 'spyOn(searchServ, 'fetch').andCallThrough()') in test/spec/controllers/main.js (line 15)
Any idea how I can test this such that it tests the scope binding as well as the async call?
There are a lot of issues with your code.
I've created this Plunkr for the purpose. index.js is the file with your code and test cases. I've edited most of the part according to the conventions and best-practices.
There are a few pointers I wanted to give you:
Since $http returns a promise, you should use that, instead of resolving the promise and creating another promise from your method. Not sure why is timeout used. So I removed $q and $timeout from searchServ's dependencies.
I did the same in the test case by removing the deferred variable that you used.
You should be using angular-mocks.js to mock your services and other dependencies instead of defining a service inside your test case(The way you have did.)
You should create separate describe blocks for testing different parts of your code(a controller in this case).
Hope this helps!
I am using the AngularBooter for Angular in my app.
Here is my code:
emp.boot();
emp.services.SocialService = ['$http', '$q', function ($http, $q) {
var deferred = $q.defer();
$http.get('/api/feed').then(function (data) {
deferred.resolve(data);
});
this.getStatuses = function ()
{
return deferred.promise;
}
}];
emp.controllers.StatusesController = ['$scope','SocialService', '$interval', function($scope, SocialService, $interval) {
var statuses = SocialService.getStatuses();
statuses.then(function(data) {
$scope.statuses = data;
console.log($scope.statuses);
});
}];
From what I understand the error: Error: [$injector:unpr] is a sign that the service isn't being registered or implemented correctly, I have tried a few things but keep running into the same problem. Any ideas.
Two problems:
1) You are booting before you register your stuff (at least in the code you provided). Do emp.boot() right at the bottom after everything has been defined.
2) Your service is not returning anything. For instance, if you add return {}; at the very bottom of your service function, it should magically not throw the error anymore, but you probably want to return something more interesting than that.
You can see this in action by uncommenting the line that returns the object and see Angular start working again.
http://codepen.io/alex-wilmer/pen/QbwNGz?editors=101
I imagine you probably want something like this:
emp.services.SocialService = ['$http', '$q', function ($http, $q) {
var deferred = $q.defer();
$http.get('/api/feed').then(function (data) {
deferred.resolve(data);
});
return {
getStatuses = function () { // don't mix brace placement!
return deferred.promise;
}
};
}];
I'm not able to get the data binding between controller and service working.
I have a controller and a factory which makes an HTTP call. I would like to be able to call the factory method from other services and see the controller attributes get updated. I tried different options but none of them seem to be working. Any ideas would be greatly appreciated.
Please see the code here:
http://plnkr.co/edit/d3c16z?p=preview
Here is the javascript code.
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
});
app.controller('EventDetailCtrl', ['$http', 'EventDetailSvc', '$scope',
function ($http, EventDetailSvc, $scope) {
this.event = EventDetailSvc.event;
EventDetailSvc.getEvent();
console.log(self.event);
$scope.$watch(angular.bind(this, function () {
console.log('under watch');
console.log(this.event);
return this.event;
}), function (newVal, oldVal) {
console.log('under watch2');
console.log(newVal);
this.event = newVal;
});
}])
.factory('EventDetailSvc', ['$http', function ($http) {
var event = {};
var factory = {};
factory.getEvent = function() {
$http.get('http://ip.jsontest.com')
.then(function (response) {
this.event = response.data;
console.log('http successful');
console.log(this.event);
return this.event;
}, function (errResponse) {
console.error("error while retrieving event");
})
};
factory.event = event;
return factory;
}]);
It seems to me that you have nested the event object inside of a factory object. You should be returning event directly instead wrapping it with factory. As it stands now you would need to call EventDetailSvc.factory.event to access your object.
I would like to unit test the following AngularJs service:
.factory('httpResponseInterceptor', ['$q', '$location', '$window', 'CONTEXT_PATH', function($q, $location, $window, contextPath){
return {
response : function (response) {
//Will only be called for HTTP up to 300
return response;
},
responseError: function (rejection) {
if(rejection.status === 405 || rejection.status === 401) {
$window.location.href = contextPath + '/signin';
}
return $q.reject(rejection);
}
};
}]);
I have tried with the following suite:
describe('Controllers', function () {
var $scope, ctrl;
beforeEach(module('curriculumModule'));
beforeEach(module('curriculumControllerModule'));
beforeEach(module('curriculumServiceModule'));
beforeEach(module(function($provide) {
$provide.constant('CONTEXT_PATH', 'bignibou'); // override contextPath here
}));
describe('CreateCurriculumCtrl', function () {
var mockBackend, location, _window;
beforeEach(inject(function ($rootScope, $controller, $httpBackend, $location, $window) {
mockBackend = $httpBackend;
location = $location;
_window = $window;
$scope = $rootScope.$new();
ctrl = $controller('CreateCurriculumCtrl', {
$scope: $scope
});
}));
it('should redirect to /signin if 401 or 405', function () {
mockBackend.whenGET('bignibou/utils/findLanguagesByLanguageStartingWith.json?language=fran').respond([{"description":"Français","id":46,"version":0}]);
mockBackend.whenPOST('bignibou/curriculum/new').respond(function(method, url, data, headers){
return [401];
});
$scope.saveCurriculum();
mockBackend.flush();
expect(_window.location.href).toEqual("/bignibou/signin");
});
});
});
However, it fails with the following error message:
PhantomJS 1.9.2 (Linux) Controllers CreateCurriculumCtrl should redirect to /signin if 401 or 405 FAILED
Expected 'http://localhost:9876/context.html' to equal '/bignibou/signin'.
PhantomJS 1.9.2 (Linux) ERROR
Some of your tests did a full page reload!
I am not sure what is going wrong and why. Can anyone please help?
I just want to ensure the $window.location.href is equal to '/bignibou/signin'.
edit 1:
I managed to get it to work as follows (thanks to "dskh"):
beforeEach(module('config', function($provide){
$provide.value('$window', {location:{href:'dummy'}});
}));
You can inject stub dependencies when you load in your module:
angular.mock.module('curriculumModule', function($provide){
$provide.value('$window', {location:{href:'dummy'}});
});
To get this to work for me I had to make a minor adjustment. It would error out and say:
TypeError: 'undefined' is not an object (evaluating '$window.navigator.userAgent')
So I added the navigator.userAgent object to get it to work for me.
$provide.value('$window', {
location:{
href:'dummy'
},
navigator:{
userAgent:{}
}
});
I faced the same problem, and went a step further in my solution. I didn't just want a mock, I wanted to replace $window.location.href with a Jasmine spy for the better ability to track changes made to it. So, I learned from apsiller's example for spying on getters/setters and after creating my mock, I was able to spy on the property I wanted.
First, here's a suite that shows how I mocked $window, with a test to demonstrate that the spy works as expected:
describe("The Thing", function() {
var $window;
beforeEach(function() {
module("app", function ($provide) {
$provide.value("$window", {
//this creates a copy that we can edit later
location: angular.extend({}, window.location)
});
});
inject(function (_$window_) {
$window = _$window_;
});
});
it("should track calls to $window.location.href", function() {
var hrefSpy = spyOnProperty($window.location, 'href', 'set');
console.log($window.location.href);
$window.location.href = "https://www.google.com/";
console.log($window.location.href);
expect(hrefSpy).toHaveBeenCalled();
expect(hrefSpy).toHaveBeenCalledWith("https://www.google.com/");
});
});
As you can see above, the spy is generated by calling the below function: (it works for both get and set)
function spyOnProperty(obj, propertyName, accessType) {
var desc = Object.getOwnPropertyDescriptor(obj, propertyName);
if (desc.hasOwnProperty("value")) {
//property is a value, not a getter/setter - convert it
var value = desc.value;
desc = {
get: function() { return value; },
set: function(input) { value = input; }
}
}
var spy = jasmine.createSpy(propertyName, desc[accessType]).and.callThrough();
desc[accessType] = spy;
Object.defineProperty(obj, propertyName, desc);
return spy;
}
Lastly, here's a fiddle demonstrating this in action. I've tested this against Angular 1.4, and Jasmine 2.3 and 2.4.