I tried to prepare http request url in interceptors rather than adding it to the object passed in $http. Here is the sample of code I tried:
angular.module('myapp', [])
.service('myservice', function() {
this.myfunction = function() {
var req = {method: 'POST', reqName: 'getInfo'};
return $http(req);
}
})
.factory('myInterceptor', function() {
var interceptor = {
'request': function(config) {
config.url = "http://www.myapi.com/demo/"+config.reqName;
return config;
}
}
return interceptor;
})
.config(function($httpProvider) {
$httpProvider.interceptors.push('myInterceptor');
})
But I'm getting an error, which says:
Error: [$http:badreq] Http request configuration url must be a string.
Received: undefined
Any help?
Let me show some tests that proof AngularJS documentation is correct.
Some details about Interceptor - http://docs.angularjs.org/api/ng/service/$http#interceptors
angular.module('myApp', [])
.service('service', function($http) {
this.myfunction = function() {
var req = {
method: 'POST',
reqName: 'getInfo'
};
return $http(req);
}
})
.factory('myInterceptor', function() {
var interceptor = {
'request': function(config) {
config.url = "http://www.myapi.com/demo/" + config.reqName;
return config;
}
}
return interceptor;
})
.config(function($httpProvider) {
$httpProvider.interceptors.push('myInterceptor');
})
describe("myApp", function() {
beforeEach(module("myApp"));
it("executes intercpetor that changes request", inject(function(service, $httpBackend) {
$httpBackend.expectPOST("http://www.myapi.com/demo/getInfo").respond(201, []);
service.myfunction().then(function(r) {
expect(r.config.url).toBe('http://www.myapi.com/demo/getInfo')
})
$httpBackend.flush();
}));
});
<link href="//safjanowski.github.io/jasmine-jsfiddle-pack/pack/jasmine.css" rel="stylesheet" />
<script src="//safjanowski.github.io/jasmine-jsfiddle-pack/pack/jasmine-2.0.3-concated.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular-resource.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular-mocks.js"></script>
Related
I'm using ng-resource in Angular to access a RESTful API. I created a simple factory to perform this.
.factory('Bookmark', function($resource){
return $resource('http://bookmarks-angular.herokuapp.com/api/bookmarks/:id');
})
And in the controller I used Bookmark.query:
.controller('MainController', function($scope, Category, Bookmark){
$scope.name = 'Carl';
Category.getAll(function(data){
$scope.categories = data.categories;
$scope.currentCategory = data.categories[0];
$scope.bookmarks = Bookmark.query();
});
})
I need to use Bookmark.save and Bookmark.removeand also a token in the Authorization header. Searching this is the approach I have to use:
$resource('url/to/json', {}, {
get: {
method: 'GET',
headers: { 'something': 'anything' }
}
});
But it works just to the get method and I want to use send the token in every $resource methods. I do not want to overwrite all the methods just to send this header. Is there any other solution?
You can create an httpInterceptor, something like this from the angular docs:
$provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
return {
'request': function(config) {
// add headers
return config;
}
}
https://docs.angularjs.org/api/ng/service/$http
This might help you in creating interceptor.
http://ramanshankar.blogspot.in/2015/11/angularjs-http-interceptor.html
var app = angular.module('simpleDirective', []);
app.config(['$httpProvider', function($httpProvider){
$httpProvider.interceptors.push('httpInterceptor');
}]);
app.controller("DemoController", ['$rootScope','demoService', function($rootScope, demoService){
$rootScope.message = [];
$rootScope.message.push("In Demo Controller");
demoService.functionOne();
}]);
app.service("demoService", ['$http','$rootScope',function($http, $rootScope){
this.functionOne = function(){
$http.get("temp.html")
.success(function(data){
$rootScope.message.push(data);
})
.error(function(data){
$rootScope.message.push("Demo Service Error: "+data);
});
}
}]);
app.factory("httpInterceptor", function(){
return{
'request': function(config) {
alert("request");
return config;
},
'requestError': function(rejection) {
alert("request error");
return "Request error";
},
'response': function(response) {
alert("response");
return response;
},
'responseError': function(rejection) {
alert("response error");
return "response error";
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="simpleDirective">
<div ng-controller="DemoController">
<h2>AngularJS Interceptor Example</h2>
<div ng-repeat="msg in message">
<div style="padding:5px;background:lightgrey;border:1px solid black;margin-bottom:10px;">{{msg}}</div>
</div>
</div>
</div>
I am trying to use angular-jwt in service but it does not append authorization on http call:
angular.module('TestApp')
.config(function Config($httpProvider, jwtInterceptorProvider) {
jwtInterceptorProvider.tokenGetter = ['merchantService', function(merchantService) {
merchantService.doSomething();
return localStorage.getItem('token');
}];
$httpProvider.interceptors.push('jwtInterceptor');
})
.service('Merchant', ['$http', function($http) {
var baseUrl = "http://test.com";
return {
terminal: function(success, error) {
$http.jsonp(baseUrl + '/tests?callback=jsonp_callback').success(success).error(error)
}
}
}]);
angular.module('alertApp', [
'alertApp.controllers',
'alertApp.services'
]);
angular.module('alertApp.services', []).
factory('alertAPIservice', function($http) {
var alertAPI = {};
alertAPI.getAlerts = function() {
return $http({
method: 'JSONP',
url: 'http://localhost:50828/api/alert'
});
}
return alertAPI;
});
angular.module('alertApp.controllers', [])
.controller('mainController', function($scope, alertAPIservice) {
$scope.message = 'Hello Mid-World!';
$scope.alertsList = [];
alertAPIservice.getAlerts().success(function (response) {
$scope.alertsList = response;
});
});
My app runs fine without errors and I can see the $scope.message displayed on the page. In fiddler I can see that my api call returns a 200 message, but the success function is never called. What have I done wrong
UPDATE
I Changed to:
alertAPIservice.getAlerts().then(function successCallback(response) {
$scope.alertsList = response;
}, function errorCallback(response) {
console.log("turd");
});
And although I receieve a 200 in fiddler, the error callback is called. The response is from web api and is of type Ok();
You need to use the name of the callback as "JSON_CALLBACK".
Please refer your updated code as below -
angular.module('alertApp', ['alertApp.controllers','alertApp.services']);
angular.module('alertApp.services', []).factory('alertAPIservice', function($http) {
var alertAPI = {};
alertAPI.getAlerts = function() {
return $http.jsonp('https://angularjs.org/greet.php?name=StackOverflow&callback=JSON_CALLBACK');
//use &callback=JSON_CALLBACK' in url
}
return alertAPI;
});
angular.module('alertApp.controllers', [])
.controller('mainController', function($scope, alertAPIservice) {
$scope.message = 'Hello Mid-World!';
$scope.alertsList = "loading data";
alertAPIservice.getAlerts().then(function (response) {
$scope.alertsList = response.data;
},function(error,a,b){
$scope.alertsList = error;
});
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="alertApp">
<div ng-controller="mainController">
{{message}}
<div>
<pre>{{alertsList|json}}</pre>
</div>
</div>
</body>
you can refer jsonp documentation here.
Hope this helps you!
Try this one ('then' instead of 'success' ):
alertAPIservice.getAlerts().then(function (response) {
$scope.alertsList = response;
});
I am trying to develop a Unit Test using Jasmine for my AngularJS User service that relies on $resource. My test which is:
'use strict';
describe("User Service Test", function() {
var service;
var mockLoginUser = { email: 'hidden', password: "hidden" };
beforeEach(module('flightlottery.userApi'));
beforeEach(inject(function(User) {
service = User;
// $scope = _$scope_;
//http = $httpBacked;
}));
it('should fetch login a user', function(done) {
var testUser = function(user) {
console.log('callback called');
//expect(user.email).toBe(mockLoginUser.email);
//expect(user.password).toBe(mockUser.password);
};
var failTest = function(error) {
expect(error).toBeUndefined();
};
//http.expectPost('/users/login', mockLoginUser).respond(200,'');
//http.expectGET('/employees/1').respond(200,mockEmployee);
service.login(mockLoginUser)
.$promise.then(testUser)
.catch(failTest)
.finally(done);
// $scope.$apply;
//http.flush();
});
});
When I run my test I get the following error.
Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
Can anyone let me know how to accomplish this? I find it rather confusing.
Thanks!
***EDIT:
Here is my user service. I would like to test the login method.
angular.module('flightlottery.userApi', ['ngResource']).
factory('User', function($resource, $rootScope) {
var current_user;
var User = $resource('http://somesite.ca/api/users/:method/:id', {}, {
query: {method:'GET', params: {method:'index'}, isArray:true },
save: {method:'POST', params: {method:'save'} },
get: {method:'GET', params: {method:'edit'} },
phistory: {method:'GET', params: {method:'history'}, isArray:true },
remove: {method:'DELETE', params: {method:'remove'} },
login: {method:'POST', params: {method:'login'} },
logout: {method:'POST', params: {method:'logout'} },
register: {method:'POST', params: {method:'register'} }
});
User.setCurrentUser = function(user) {
//var self = this;
current_user = user;
$rootScope.$broadcast('user:updated',user);
//console.log(self.current_user_id);
}
User.getCurrentUser = function() {
//var self = this;
return current_user;
//console.log(self.current_user_id);
}
User.registerUser = function(cb) {
//console.log(cb);
return User.register(cb);
}
User.play_history = function(cb) {
//console.log(cb);
return User.phistory(cb);
}
User.loginUser = function(cb) {
return User.login(cb);
}
User.logoutUser = function(cb) {
current_user = null;
return User.logout(cb);
// return User.logout();
///return User.save({id: this.id},
//angular.extend({}, this, {id:undefined}), cb);
};
User.prototype.update = function(cb) {
return User.save({id: this.id},
angular.extend({}, this, {id:undefined}), cb);
};
User.prototype.destroy = function(cb) {
return User.remove({id: this.id}, cb);
};
return User;
});
After trying to guess what you want to accomplish, little refactoring – the results looks like this.
I'm always wonder what really we want test and what we want to prove if test pass or fail.
Version with dependencies stubbed
angular.module('flightlottery.userApi', ['ngResource'])
.factory('User', function($resource) {
var User = $resource('http://somesite.ca/api/users/:method/:id', {}, {
login: {
method: 'POST',
params: {
method: 'login'
}
}
});
User.loginUser = function(cb) {
return User.login(cb);
}
return User;
})
describe("User Service Test", function() {
var service;
var queryDeferred;
var mockLoginUser = {
email: 'hidden',
password: "hidden"
};
var scenarios = {
success: function(user) {
expect(user.email).toBe(mockLoginUser.email);
expect(user.password).toBe(mockLoginUser.password);
},
fail: function(error) {
expect(error).toBeDefined();
}
}
beforeEach(module('flightlottery.userApi'));
beforeEach(inject(function(_$rootScope_) {
$rootScope = _$rootScope_;
}));
beforeEach(inject(function($q) {
queryDeferred = $q.defer();
mockUserLogin = {
login: function() {
return {
$promise: queryDeferred.promise
};
}
}
spyOn(mockUserLogin, 'login').and.callThrough();
spyOn(scenarios, 'success').and.callThrough();
spyOn(scenarios, 'fail').and.callThrough();
}))
it('runs `success scenario` if user object is fetched', function() {
queryDeferred.resolve(mockLoginUser)
userLogin(mockLoginUser, scenarios);
expect(scenarios.success).toHaveBeenCalled()
expect(scenarios.fail).not.toHaveBeenCalled()
});
it('runs `fail scenario` if user object is not fetched', function() {
var reason = {
error: 'some error'
}
queryDeferred.reject(reason)
userLogin(mockLoginUser, scenarios)
expect(scenarios.success).not.toHaveBeenCalled()
expect(scenarios.fail).toHaveBeenCalledWith(reason)
});
function userLogin(mockLoginUser, scenarios) {
mockUserLogin.login(mockLoginUser)
.$promise.then(scenarios.success)
.catch(scenarios.fail)
.finally();
$rootScope.$apply();
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link href="//safjanowski.github.io/jasmine-jsfiddle-pack/pack/jasmine.css" rel="stylesheet" />
<script src="//safjanowski.github.io/jasmine-jsfiddle-pack/pack/jasmine-2.0.3-concated.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular-resource.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular-mocks.js"></script>
Second version - injected service and $httpBackend
When an Angular application needs some data from a server, it calls the $http service, which sends the request to a real server using $httpBackend service. With dependency injection, it is easy to inject $httpBackend mock (which has the same API as $httpBackend) and use it to verify the requests and respond with some testing data without sending a request to a real server.
angular.module('flightlottery.userApi', ['ngResource']).
factory('User', function($resource, $rootScope) {
var User = $resource('http://somesite.ca/api/users/:method/:id', {}, {
login: {
method: 'POST',
params: {
method: 'login'
}
}
});
User.loginUser = function(cb) {
return User.login(cb);
}
return User;
});
describe("User Service Test", function() {
var $httpBackend, User;
beforeEach(module('flightlottery.userApi'));
beforeEach(inject(function(_$httpBackend_, _User_) {
$httpBackend = _$httpBackend_;
User = _User_;
}));
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('calls `POST` method to interacts with backend', function() {
var stubUser = {
name: 'Some name',
password: 'somePassword'
};
var stubResponse = {
login: 'someName',
lastLogin: Date.now()
}
spyOn(User, 'login').and.callThrough();
User.loginUser(stubUser).$promise.then(function(response) { // response is stubbed by second argument of repond method
expect(response.login).toBe(stubResponse.login)
expect(response.lastLogin).toBe(stubResponse.lastLogin)
});
expect(User.login).toHaveBeenCalledWith(stubUser)
$httpBackend.expectPOST('http://somesite.ca/api/users/login').respond(200, stubResponse)
$httpBackend.flush();
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link href="//safjanowski.github.io/jasmine-jsfiddle-pack/pack/jasmine.css" rel="stylesheet" />
<script src="//safjanowski.github.io/jasmine-jsfiddle-pack/pack/jasmine-2.0.3-concated.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular-resource.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular-mocks.js"></script>
I'm playing around with the code of
http://ngmodules.org/modules/http-auth-interceptor
and I'm wondering why
$rootScope.$broadcast('loginRequired');
doesn't trigger the alert in the controller
$scope.$on('loginRequired',function() {
alert('loginRequired');
});
The code:
<!doctype html>
<html ng-app="myModule">
<head>
<meta charset="utf-8">
</head>
<body>
<div id="content" class="ng-view"></div>
<script src="http://code.angularjs.org/1.1.5/angular.min.js"></script>
<script>
var buffer = angular.module('http-auth-interceptor-buffer', []);
buffer.factory('httpBuffer', function($injector) {
var buffer = [];
var $http;
function retryHttpRequest(config, deferred) {
function successCallback(response) {
deferred.resolve(response);
}
function errorCallback(response) {
deferred.reject(response);
}
$http = $http || $injector.get('$http');
$http(config).then(successCallback, errorCallback);
}
return {
append: function(config, deferred) {
buffer.push({
config: config,
deferred: deferred
});
},
retryAll: function(updater) {
for (var i = 0; i < buffer.length; ++i) {
retryHttpRequest(updater(buffer[i].config), buffer[i].deferred);
}
buffer = [];
}
};
});
var app = angular.module('myModule', ['http-auth-interceptor-buffer']);
app.config(function($httpProvider,$routeProvider, $locationProvider) {
$httpProvider.interceptors.push('securityInterceptor');
$routeProvider.
when('/one',{
controller: 'OneCtrl',
/*resolve: {
my: function(Data) {
return Data.getData();
}
},*/
templateUrl: './_one.html'
}).
when('/two', {
controller: 'TwoCtrl',
templateUrl:'./_two.html'
})
.otherwise({
redirectTo: '/one'
});
});
app.controller('OneCtrl',function($scope,Data) {
$scope.my = Data.getData();
$scope.$on('loginRequired',function() {
alert('loginRequired');
});
});
app.controller('TwoCtrl',function($scope) {
});
app.factory('Data', function($http,$q) {
return {
getData : function() {
var deferred = $q.defer();
var promise = $http.get('./security.php').success(function (response) {
deferred.resolve(response);
});
// Return the promise to the controller
return deferred.promise;
}
}
});
app.factory('securityInterceptor', function($q, $rootScope,httpBuffer) {
return {
request: function(config) {
return config || $q.when(config);
},
requestError: function(rejection) {
},
response: function(response) {
return response || $q.when(response);
},
responseError: function(rejection) {
if(rejection.status === 401) {
var deferred = $q.defer();
httpBuffer.append(rejection.config, deferred);
$rootScope.$broadcast('loginRequired');
return deferred.promise;
}
return $q.reject(rejection);
}
};
});
</script>
</body>
</html>
UPDATE
security.php
<?php
header('HTTP/1.1 401 Unauthorized');
$data = 'MyTets';
echo json_encode($data);
What's the trouble ?
I tested your code by putting
$rootScope.$broadcast('loginRequired');
in securityInterceptor
response: function(response) {
$rootScope.$broadcast('loginRequired');
return response || $q.when(response);
},
had problem to simulate the response of the php file. Your code and the events are working well. It should come from your php file which didn't return 401 right?
here is a plunker here