Angularjs + two http request using $q - javascript

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];
});

Related

Asynchronously function returns undefined with $q deferred

I am calling my data from my api through a factory that looks like this:
app.factory('Service', ['$http', function ($http) {
var urlBase = 'http://localhost:50476/api';
var Service = {};
Service.getComp = function () {
return $http.get(urlBase + '/complaints')
};
return Service;
}]);
Then I use my controller to use the directive:
getComp();
$scope.comp = [];
function getComp() {
var deferred = $q.defer();
Service.getComp()
.success(function (comp) {
console.log('comp', comp); //returns array data
$scope.comp = comp.data;
deferred.resolve(comp);
})
.error(function (error) {
$scope.error = 'error' + error.message;
});
return deferred.promise;
}
$scope.index = 0;
$scope.complaints = $scope.comp[0];
console.log($scope.complaints); //undefined
console.log($scope.comp); //array of 0
When I try to access the items outside of the function it is undefined. I tried to look for resolutions like using $q but it is still not displaying data. When I added the deferred part my ng-repeat stops working as well.
Try this:
getComp();
$scope.comp = [];
function getComp() {
return Service.getComp()
.success(function (comp) {
$scope.comp = comp.data;
$scope.complaints = $scope.comp[0];
})
.error(function (error) {
$scope.error = 'error' + error.message;
});
}
The values are undefined when you do your logs because those lines run before your request comes back from the server. That's why setting $scope.complaints has to go into the success callback.
if you want to make sure complaints are loaded on certain states before you start your logic you can use ui-routers resolve keyword (i suppose you are using ui-router with ionic - standard package)
In you main.js
$stateProvider.state('yourState', {
resolve: {
complaints: function(Service) {
return Service.getComp();
}
}
});
in your controller you can then inject complaints
.controller('myController', function(complaints) {
$scope.complaints = complaints;
})
resolve at $stateProvider will block and wait for the promise to resolve...

Authenticate before calling REST endpoint

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.

Angularness authenticating user with resolve and http.get request

Right now I have a simple angular set up that has a login state and a cloud state. I want to make it so the cloud state can only be run if a user is authenticated. And if not, it will direct them to the login state.
So far I believe I have the "resolve" setup and I have the .run() function set up to redirect to the login state if there the resolve fails.
My only issue is, no matter what, my authenticated variable get's returned un-defined. I'm not sure how to incorporate the $http.get.
I'm new to angular, so If anyone has any suggestions, I'd gladly appreciate them.
var routerApp = angular.module('app', ['ui.router'])
.factory('Auth', function($http, $state, $q) {
var factory = { isLoggedIn: isLoggedIn };
return factory;
function isLoggedIn() {
$http.get('/auth/user').then(function(data) {
return true;
});
}
})
.config(function($stateProvider, $urlRouterProvider, $locationProvider) {
$locationProvider.html5Mode(true);
$urlRouterProvider.otherwise('/cloud');
var authenticated = ['$q', 'Auth', function ($q, Auth) {
var deferred = $q.defer();
if(Auth.isLoggedIn()) deferred.resolve(); else deferred.reject();
return deferred.promise;
}];
var authGuest = function($state, Auth) {
if(Auth.isLoggedIn()) $state.transitionTo('cloud');
}
$stateProvider
.state('login', {
url: '/login',
templateUrl: "pages/templates/login.html",
onEnter: authGuest
})
.state('cloud', {
url: '/cloud',
templateUrl: "pages/templates/account.html",
resolve: { authenticated: authenticated }
})
})
.run(function ($rootScope, $state, $log) {
$rootScope.$on('$stateChangeError', function () {
$state.go('login');
});
});
First thing service method should return promise from isLoggedIn, other method should be using that promise to resolve it. Then you don't need to use use Auth.isLoggedIn() in if because anyhow it would be going to return an promise with catch, then & finally in it.
function isLoggedIn() {
retutn $http.get('/auth/user');
}
Then those method would be resolved using promise
var authenticated = ['$q', 'Auth', function ($q, Auth) {
var deferred = $q.defer();
Auth.isLoggedIn().then(function(){
deferred.resolve();
}, function(error){
deferred.reject();
});
return deferred.promise;
}];
var authGuest = function($state, Auth) {
Auth.isLoggedIn().then(function(){
$state.transitionTo('cloud');
})
}

AngularJS Promise error catching

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.

How can I simulate a network delay when unit testing an AngularJS controller?

I am using the $httpBackend service in module ngMock to simulate a GET request. From the AngularJS documentation, here is an example controller:
// The controller code
function MyController($scope, $http) {
var authToken;
$http.get('/auth.py').success(function(data, status, headers) {
authToken = headers('A-Token');
$scope.user = data;
});
$scope.saveMessage = function(message) {
var headers = { 'Authorization': authToken };
$scope.status = 'Saving...';
$http.post('/add-msg.py', message, { headers: headers } ).success(function(response) {
$scope.status = '';
}).error(function() {
$scope.status = 'ERROR!';
});
};
}
and, here is the corresponding Jasmine test spec:
// testing controller
describe('MyController', function() {
var $httpBackend, $rootScope, createController;
beforeEach(inject(function($injector) {
// Set up the mock http service responses
$httpBackend = $injector.get('$httpBackend');
// backend definition common for all tests
$httpBackend.when('GET', '/auth.py').respond({userId: 'userX'}, {'A-Token': 'xxx'});
// Get hold of a scope (i.e. the root scope)
$rootScope = $injector.get('$rootScope');
// The $controller service is used to create instances of controllers
var $controller = $injector.get('$controller');
createController = function() {
return $controller('MyController', {'$scope' : $rootScope });
};
}));
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('should fetch authentication token', function() {
$httpBackend.expectGET('/auth.py');
var controller = createController();
$httpBackend.flush();
});
it('should send msg to server', function() {
var controller = createController();
$httpBackend.flush();
// now you don’t care about the authentication, but
// the controller will still send the request and
// $httpBackend will respond without you having to
// specify the expectation and response for this request
$httpBackend.expectPOST('/add-msg.py', 'message content').respond(201, '');
$rootScope.saveMessage('message content');
expect($rootScope.status).toBe('Saving...');
$httpBackend.flush();
expect($rootScope.status).toBe('');
});
it('should send auth header', function() {
var controller = createController();
$httpBackend.flush();
$httpBackend.expectPOST('/add-msg.py', undefined, function(headers) {
// check if the header was send, if it wasn't the expectation won't
// match the request and the test will fail
return headers['Authorization'] == 'xxx';
}).respond(201, '');
$rootScope.saveMessage('whatever');
$httpBackend.flush();
});
});
As written above, the mock request responds instantly when executing the tests. I would like to set a delay on the mock GET request. Is this possible? I have a feeling the $timeout service will be needed to achieve this.
Bonus Question: Are there any drawbacks to setting a delay like this? Is this a reasonable thing to do in an AngularJS unit test?
Create a decorator for $httpBackend, like this one: http://endlessindirection.wordpress.com/2013/05/18/angularjs-delay-response-from-httpbackend/
Put this in your app.js, for a 700ms delay during each mocked or passthrough response:
.config(function($provide) {
$provide.decorator('$httpBackend', function($delegate) {
var proxy = function(method, url, data, callback, headers) {
var interceptor = function() {
var _this = this,
_arguments = arguments;
setTimeout(function() {
callback.apply(_this, _arguments);
}, 700);
};
return $delegate.call(this, method, url, data, interceptor, headers);
};
for(var key in $delegate) {
proxy[key] = $delegate[key];
}
return proxy;
});
})
Bonus Answer: I believe time is not something you would test in an unit test. But for sure you need to test for server errors, that's why mocked http comes handy.
And I use delay like this when prototyping, that's a valid scenario too.

Categories