There is the following code:
angular.module('app.services', []).factory('authService', [
'SIGN_IN_ENDPOINT', 'SIGN_OUT_ENDPOINT', '$http', '$cookieStore', function(SIGN_IN_ENDPOINT, SIGN_OUT_ENDPOINT, $http, $cookieStore) {
var auth;
auth = {};
auth.signIn = function(credentials) {
return $http.post(SIGN_IN_ENDPOINT, {
user: credentials
}).then(function(response, status) {
return $cookieStore.put('user', response.data);
}, function(error) {
return console.log("Incorrect email/password");
});
};
return auth;
}
This is my module for authentication. Now I have the following function in controller:
angular.module('app.admin.controllers', []).controller('SignInController', [
'$scope', 'authService', '$state', function($scope, authService, $state) {
$scope.buttonText = "Login";
return $scope.login = function() {
$scope.buttonText = "Logging in. . .";
return authService.signIn($scope.credentials).then(function(response, status) {
return $state.go('allPosts');
}, function(err) {
$scope.invalidLogin = true;
return $scope.buttonText = "Login";
});
};
}
The problem: if I input wrong email/password, I'm waiting for 2 error callbacks - from the first 'then' from the second one, but I catch the first error callback (I can see console log) and after it THE SUCCESS callback executes! (return $state.go('allPosts')). Why? The Response from the server is 401 error.
The reason for this is, that you catch the error in the "app.services" and dont "bubble" the problem to higher tiers.
angular.module('app.services', []).factory('authService', [
'SIGN_IN_ENDPOINT', 'SIGN_OUT_ENDPOINT', '$http', '$cookieStore', function(SIGN_IN_ENDPOINT, SIGN_OUT_ENDPOINT, $http, $cookieStore) {
var auth;
auth = {};
auth.signIn = function(credentials) {
return $http.post(SIGN_IN_ENDPOINT, {
user: credentials
}).then(function(response, status) {
return $cookieStore.put('user', response.data);
}, function(error) {
console.log("Incorrect email/password");
return $q.reject(); //here is the important one.
});
};
return auth;
}
Or completely miss out the error handler.
auth.signIn = function(credentials) {
return $http.post(SIGN_IN_ENDPOINT, {
user: credentials
}).then(function(response, status) {
return $cookieStore.put('user', response.data);
});
};
If you catch the error and return a value within the error, following promises dont know about the occured error.
Since the auth service is returning the promise returned by then function, in the first error callback you need to return rejected promise.
You can do it in this way:
auth.signIn = function(credentials) {
return $http.post(SIGN_IN_ENDPOINT, {
user: credentials
}).then(function(response, status) {
return $cookieStore.put('user', response.data);
}, function(error) {
console.log("Incorrect email/password");
return $q.reject(error); // return a rejected promise;
});
};
Also remember to inject $q into your service, for this to work.
The promise returned by then is resolved with the return value of success and error callback functions.
If you do a standard return, you will always land up on the success path.
When you return $q.reject you are returning a promise that is eventually rejected. See documentation on $q.
You can also throw an exception from error callback in the service and get the same result.
Related
I'm confused or may be didn't understand how angular promise works. I am trying to write some code blocks for my error handling but I see it's always executing success block in my controller. However I've written success and error block in my service too because I need some transformation in my response. I see it's executing error block in service which is perfectly fine but the same promise executes success block in my controller.
HTML
<div ng-app="myApp">
<div ng-controller="MainController">
<h1>{{data}}</h1>
</div>
<div>
JS
angular.module('services', []).service('myService', function($http) {
this.getData = function() {
return $http.get('test.json').then(function (response) {
console.log(response);
return response.data;
},function(data) {
console.log("Error block of service");
});
}
});
var app = angular.module('myApp', ['services']);
app.controller('MainController', ['$scope', 'myService', function ($scope, myService) {
// Call the getData and set the response "data" in your scope.
myService.getData().then(function(myReponseData) {
console.log("Success block of controller");
$scope.data = myReponseData;
},function(data) {
console.log("Error block of controller");
$scope.data = "Error " + data;
});
}]);
I reproduce the same issue in fiddle. Have a look JSFiddle
Because that's how Promise is designed to work.
If you return anything inside of the catch block, whatever you return becomes a success for the next link in the chain.
The only two ways around that are:
Rethrow an error inside of your catch
Return a rejected Promise inside of your catch
Here's a simpler example:
Promise.reject(5)
.catch(x => x * 2)
.catch(err => console.log("THIS NEVER FIRES"))
.then(x => console.log("Value is: ", x));
// => "Value is: 10"
Promise.reject(5)
.catch(x => Promise.reject(x * 2))
.then(x => console.log("THIS NEVER FIRES"))
.catch(err => console.log("Error is:", err));
// => "Error is: 10"
In rejection handlers, it is important to re-throw errors. Otherwise the rejected promise will be converted to a successful promise:
angular.module('services', []).service('myService', function($http) {
this.getData = function() {
return $http.get('test.json').then(function (response) {
console.log(response);
return response.data;
},function(errorResponse) {
console.log("Error block of service");
//IMPORTANT re-throw error
throw errorResponse;
});
}
});
For more information, see You're Missing the Point of Promises.
I`ve got a service where I call http,the problem is that I dont understand how to use $scope.photos in the controller.How to config controller and service to do this?
app.service('http',function($scope,$http){
function getAll() {
$http.get('/allphotos').then(function success(response) {
$scope.photos = response.data;
}, function error(err) {
console.log('error is:' + err.err)
});
}
});
app.controller('ctrl',function ($scope, http) {
}
First of all never use $scope inside Service/Factory/Provider. The scope is related to controllers only, a.e. deals with bind views with controllers.
You have a method getAll() that should return Promise and you will be able to resolve it inside your controller.
Something like:
app.service('httpService',function(,$http){
this.getAll = function() {
return $http.get('/allphotos').then(function(response) {
// ^ 'getAll' returns Original Promis comes from '$http'
return response.data;
// ^ here we resolve 'response.data'
}, function(err) {
console.log('error is:' + err.err)
});
}
});
And now in controller do:
app.controller('ctrl',function ($scope, httpService) {
httpService.getAll().then(function(data) {
$scope.data = data;
}, function error(err) {
console.log('error is:' + err.err)
});
}
I have two http requests.
1. post method
2. get method.
I need to get data from two http request.
I added my code. But i think this is not correct approach. Please suggest me.
Question 1:
After two call to synchronous.
Controller
(function() {
'use strict';
angular.module('myApp').controller('loginController', loginController);
loginController.$inject = ['$auth', '$http', '$location', '$scope', '$window', '$rootScope', 'commonService', '$q', '$localStorage'];
// inject an auth service here!
function loginController($auth, $http, $location, $scope, $window, $rootScope, commonService, $q, $localStorage) {
commonService.getHostDetails().then(function(data) {
commonService.login().then(function(data) {
}).catch(function(data) {
alert('Sorry ! test function faild');
});
}).catch(function(data) {
alert('Sorry ! login function faild');
});
};
})();
Service Code:
angular.module('myApp').factory('commonService', ['$q', '$timeout', '$http',
function($q, $timeout, $http, commonService) {
return ( {
login : login,
test : test,
});
function login() {
// create a new instance of deferred
var deferred = $q.defer();
$http.get('host.json')
// handle success
.success(function(data, status) {
deferred.resolve(data);
})
// handle error
.error(function(data) {
deferred.reject(data);
});
// return promise object
return deferred.promise;
}
function test(formData) {
// create a new instance of deferred
var deferred = $q.defer();
console.log("in service");
console.log(formData);
$http.post('/api/test', formData, {
headers : {
'Content-Type' : 'application/json'
}
})
// handle success
.success(function(data) {
deferred.resolve(data);
})
// handle error
.error(function(data) {
deferred.reject(data);
});
// return promise object
return deferred.promise;
}
}]);
Question -2.
Also please suggestme two http request depends on two api.
First http request data need to parse into second api.
Promises are a great way of chaining you calls.
$http.post("your api").then(function(response) {
//do something
return response.data;
}).then(function(data) {
return $http.get("you other api")
}).then(response_from_second_api) {
});
If you have two calls then need to be resolved before you can do anything you could do something like that
var promise1 = $http.get("");
var promise2 = $http.get("");
$q.all([promise1,promise2]).then(values) {
var value_of_promise1 = values[0];
var value_of_promose2 = values[1];
});
I'm trying to get an authentication token from my REST API before calling any other endpoint (preferably once).
For this I created a token factory that calls the login and receives a token back. I then expect to inject that token factory into my other controllers. I was hoping that the dependencies where being respected but my controller calls the service before obtaining the token from the token factory. what did I do wrong ?
factory:
app.factory('tokenFactory', function($http, appConfig) {
console.log('calling endpoint: ' + appConfig.REST_ENDPOINT + 'authentication/login');
var apiToken;
$http.post(appConfig.REST_ENDPOINT + 'authentication/login', {
"username": "john",
"password": "open$esame"
}).
success(function(data) {
apiToken = data.token;
}).
error(function(data) {
//
});
return {
apiToken: apiToken
};
});
controller:
app.controller('clientListCtrl', function($scope, $http, appConfig, tokenFactory) {
console.log('calling endpoint: ' + appConfig.REST_ENDPOINT+'/client/list');
$http.get(appConfig.REST_ENDPOINT+'/client/list', {
header: { 'Authorization': tokenFactory.apiToken }
})
.success(function(data) {
$scope.clients = data;
}).
error(function(data, status) {
//
});
});
Yes, you have to take into account the asynchronous aspect of Ajax and leverage promise chaining (the $http.post actually returns a promise that you need to return). The factory will use the method getToken can define a success method to be notified when the result is received.
app.factory('tokenFactory', function($http, appConfig) {
console.log('calling endpoint: ' + appConfig.REST_ENDPOINT + 'authentication/login');
return {
getToken: function() {
return $http.post(appConfig.REST_ENDPOINT + 'authentication/login', {
"username": "john",
"password": "open$esame"
}).
success(function(data) {
return data.token;
}).
error(function(data) {
//
});
}
};
});
That said, I think that you should leverage the HTTP interceptor feature of Angular. This allows to transparently set the security token within your request. The first time the token is gotten using AJAX and then you can reuse this one.
app.factory('securityTokenInterceptor', function($q, tokenFactory) {
var currentToken = null;
return {
request: function(config) {
if (currentToken != null) {
config.headers['Authorization'] = currentToken;
return config;
}
var deferred = $q.defer();
tokenFactory.getToken().then(function(token) {
config.headers['Authorization'] = token.token;
currentToken = token.token;
deferred.resolve(config);
}, function(err) {
// Handle error (reject promise, ...)
});
return deferred.promise;
}
};
})
Here is the way to register your interceptor on $httpProvider:
app.config(function($httpProvider) {
$httpProvider.interceptors.push('securityTokenInterceptor');
})
Here is the fake factory I use to get token:
app.factory('tokenFactory', function($q, $timeout) {
return {
getToken: function() {
var deferred = $q.defer();
$timeout(function() {
deferred.resolve({token:'mytoken'});
}, 500);
return deferred.promise;
}
};
})
Hope it helps you,
Thierry
In your factory:
$http.post() is asynchronous, so the return after it will not contain the data coming from the post request. I would suggest returning the promise object you get from calling $http.post().
In your controller: you can use the returned promise and define the success method, in which you can do the get request.
tokenFactory.success(function (tokenData) {
token = tokenData.token;
$http.get(endpoint, { header: { 'auth': token } })
.success(...)
.error(...);
});
Not sure if it is the best way how to do it, but I think it could work this way.
I have a simple scenario - I wish to init my http calls with interceptor that will add a value in headers (a token of some kind).
The problem is that the token is received via http as well (it should be the first call) but I don't know how to make all other calls to wait for it to finish before issuing their own calls...
.factory('sessionData', function () {
var currentToken = '[uninitialized-token]';
return {
getToken: function () {
return currentToken;
},
setAuthData: function (token) {
currentToken = token;
}
}
})
.factory('sessionInjector', ['sessionData', function (sessionData) {
var sessionInjector = {
request: function (config) {
console.log("sending with token: " + sessionData.getToken());
config.headers['x-header-sessionID'] = sessionData.getToken();
}
};
return sessionInjector;
}])
.config(['$httpProvider', function ($httpProvider) {
$httpProvider.interceptors.push('sessionInjector');
}])
.run(['$http', 'configs', 'sessionData', function ($http, configs, sessionData) {
$http.get(configs.authApiUrl + 'login').then(function (ret) {
sessionData.setAuthData(ret);
console.log("successfully authenticated with token " + sessionData.getToken());
});
}])
.controller('TestCtrl', function($http){
$scope.p1 = 'Uninitialized';
$http.get('http://localhost/api/getData').then(function(ret){
$scope.p1 = ret;
});
});
The problem is that the TestCtrl issues an http call before the run method finished getting the token (resulting in header value having the [uninitialized-token] in it's value).
How to make the controllers wait for the 'run' async methods to finish?
$http interceptors can be used to return promises in their callbacks. You can use this to intercept each call and delay it until the promise is resolved.
You should understand how promises work for this.
Example:
myModule.factory('tokenPromise', function($http) {
return $http.get({url: 'myurl/token', bypassToken: true}).then(function(data) {
// This is when your token webservice return, deal with the response here
return data.token;
});
});
myModule.factory('myHttpInterceptor', function($q, tokenPromise) {
return {
'request': function(config) {
if (config.bypassToken) return config;
// This ensures the token promise is resolved before proceeding with the request.
return tokenPromise.then(function(token) {
config.headers['x-header-sessionID'] = token;
return config;
});
},
};
});
myModule.config(function($httpProvider) {
//wire the interceptor here
$httpProvider.interceptors.push('myHttpInterceptor');
})
reference: http service on angular official docs