Best way to initialize a controller with several ajax requests - javascript

I have 2 $http functions that I call using ng-init because the data they return populates the page.
ng-init = "getOpen(); getClosed();"
Is this the best way?
First function;
$scope.getOpen = function () {
$http({
method: 'post',
url: "http://www.example.co.uk/php/open-get.php",
data: $.param({ 'location' : $scope.l,
'time' : $scope.t,
'day' : $scope.d,
'type' : 'get_restopen' }),
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
}).
success (function(data, status, headers, config){
if(data.success && !angular.isUndefined(data.data) ){
$scope.open = data.data;
} else {
$scope.open = [];
}
}).
error(function(data, status, headers, config) {
//$scope.messageFailure(data.message);
});
}
Second function;
$scope.getClosed = function () {
$http({
method: 'post',
url: "http://www.example.co.uk/php/closed-get.php",
data: $.param({ 'location' : $scope.l,
'time' : $scope.t,
'day' : $scope.d,
'type' : 'get_restopen' }),
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
}).
success (function(data, status, headers, config){
if(data.success && !angular.isUndefined(data.data) ){
$scope.closed = data.data;
} else {
$scope.closed = [];
}
}).
error(function(data, status, headers, config) {
//$scope.messageFailure(data.message);
});
}
Everything works great. My question I guess is this an efficient way of doing things in AngularJS? I'm new to angular so just seeking guidance.
1 - Are my $http executed simultaneously? Or is one completed before the other one is started?
2 - Is there any need to introduce $q or promises into my code? The functions are independent of each other
3 - How can I detect when all $http request have completed regardless of if successful or not
Is the below code correct?
$q.all([$scope.getOpen, $scope.getClosed]).then(function() {
//Both requests have been completed and I shall now set a bolean
$scope.compelete = true;
});

Assuming you call both methods yourself somewhere, yes. $http calls are async by default
Already done, $http actually returns a promise!
promise.all() is an elegant way to do so without modifying the return of the promise. It is effectively a completion watcher. More details over on the promise reference

I have 2 $http functions that I call using ng-init because the data
they return populates the page.
ng-init = "getOpen(); getClosed();"
Is this the best way?
As said into angular documentation :
This directive can be abused to add unnecessary amounts of logic into
your templates. There are only a few appropriate uses of ngInit, such
as for aliasing special properties of ngRepeat, as seen in the demo
below; and for injecting data via server side scripting. Besides these
few cases, you should use controllers rather than ngInit to initialize
values on a scope.
You can find it here : https://docs.angularjs.org/api/ng/directive/ngInit
you should avoid using ng-init into your templates. The recommended usage for initiating something into a controller is to call a private function directly inside your controller.
I also recommend to read that style guide about angular :
https://github.com/johnpapa/angular-styleguide
1 - Are my $http executed simultaneously? Or is one completed before
the other one is started?
The 2 calls are started almost simultaneously. Then, javascript holds a stack of callbacks he has to execute when he gets answers from your server. Just press F12 into your browser and find a "network" tab to see all your requests being launched.
Furthermore, ´.success´ is deprecated now. You should rather use ´.then´
https://docs.angularjs.org/api/ng/service/$http#deprecation-notice
2 - Is there any need to introduce $q or promises into my code? The
functions are independent of each other
Not in this case. You could use $q.all([promise1, promise2]) if you wanted to execute something after both calls had been executed
3 - How can I detect when all $http request have completed regardless
of if successful or not
Have a look at $httpInterceptors. Let share you a piece of code I implemented
angular.module("myModule").factory("myHttpInterceptor", [
"LoadingService",
"$q",
"Alert",
function (LoadingService, $q, Alert) {
function requestInterceptor(config) {
LoadingService.increaseCounter();
return config;
}
function responseInterceptor(response) {
LoadingService.decreaseCounter();
return response;
}
// ...
Is the below code correct?
$q.all([$scope.getOpen, $scope.getClosed]).then(function() {
//Both requests have been completed and I shall now set a bolean
$scope.compelete = true;
});
nope because you still the promise to be returned by your function. Also, ´.success´ does not implement the chaining of promises. You now HAVE to use ´.then()´
$scope.getOpen = function () {
return $http({
// config
}).
then(function(data, status, headers, config){
//handling success
},(function(data, status, headers, config) {
//handling error
});
Summary :
angular.module("yourApp").controller("someController", ["$scope", "$q"
function($scope, $q){
// init
init();
// Shared functions
$scope.getOpen = function () {
return $http({
method: 'post',
url: "http://www.example.co.uk/php/open-get.php",
data: $.param({ 'location' : $scope.l,
'time' : $scope.t,
'day' : $scope.d,
'type' : 'get_restopen' }),
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
});
$scope.getClosed = function () {
return $http({
// config
});
// private functions
function init(){
var promises = [];
promises.push($scope.getOpen());
promises.push($scope.getClosed());
$q.all(promises).then(function(datas){
// Here, you know both calls have been successfull
// datas[0] is the result of $scope.open()
$scope.open = datas[0].data;
$scope.complete = true;
}, function(errors){
$scope.complete = true;
// You fall here if one of your calls has failed
});
}
}
]);

$http is asynchronous, so your 2 calls should be executed in parallel. If you really need to boil the 2 functions down to one, and/or you need to check all $http requests have completed, you can use $q.all Angularjs $q.all

Related

returned object is undefined in a custom ajax service

I created an AngularJS service which does ajax request to the server and returns the response object to the controller. Below is my service
app.factory('ajaxService', function() {
return {
ajaxcall: function(url, type, data, handler) {
$.ajax({
url: url,
type: type,
data: data,
beforeSend: function(xhr) {
xhr.setRequestHeader("X-OCTOBER-REQUEST-HANDLER", handler);
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
}
})
.done(function(response) {
console.log("ajaxService done: ");
console.log(response);
return response;
})
.fail(function(response) {
console.log("in onCheckUser-error: ajaxService ");
});
}
}
});
The controller is defined as below
var app = angular.module('starter', [])
app.controller('myCtrl', function(ajaxService) {
var res = {};
res = ajaxService.ajaxcall("https://www.travelmg.in/check-login",'POST','',"onCheckLogin");
console.log(res);
});
Here, i get the expected response in console in the ajaxService service. When i return the response, i see an "undefined" value in res variable in console.
I don't understand why the res variable is undefined. Please suggest
Thats because your making an asynchronous call, which means it will not return the result immediately.
only way to resolve this is to receive the promise object returned from $.ajax & use the .done() function on it to receive the successful data.
What you need to do:
Move the done() & fail() outside service factory.
return the ajax promise object all the way to the consumer, i.e controller.
JS CODE:
//service factory code
return {
ajaxcall: function(url, type, data, handler) {
return $.ajax({
url: url,
type: type,
data: data,
beforeSend: function(xhr) {
xhr.setRequestHeader("X-OCTOBER-REQUEST-HANDLER", handler);
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
}
});
}
}
//controller code
app.controller('myCtrl', function(ajaxService) {
var res = {};
ajaxService.ajaxcall("https://www.travelmg.in/check- login",'POST','',"onCheckLogin")
.done(function(response) {
console.log("ajaxService done: ");
console.log(response);
//return response; // dont return instead process the result here
})
.fail(function(response) {
console.log("in onCheckUser-error: ajaxService ");
});
});
Note:
I would personally dont prefer mixing jquery and angularjs library unless jquery is really needed for some third party library. both are two different frameworks with different ideology,so dont mix them.
also if your referring to jquery only for $.ajax api ? then i would suggest you to use $http, whose API is same as $.ajax. ref: https://docs.angularjs.org/api/ng/service/$http
You have built the application in angular so it would be convenient to use $http directive to make ajax calls. Inject $http is your service, then you can handle the response as such:
ajaxService.ajaxcall("https://www.travelmg.in/check-login",'POST','',"onCheckLogin").then(function(response) {
console.log(response.data);
});

How do you deal with asynchronous return from $http.post in angularJS?

Stuck with a simple basic login problem here. My AuthService factory has following code inside of it (2 relevant functions and a local variable):
var username = '';
function login(uname, upwd, utype) {
// create a new instance of deferred
var deferred = $q.defer();
$http({
method: 'POST',
url: '/root',
headers: {
'Content-Type': 'application/json'
},
data: {
username: uname,
password: upwd,
type: utype
}
}).success(function(data, status, headers, config) {
if (status === 200) {
user = true;
username = data.username;
usertype = data.usertype;
deferred.resolve();
} else {
user = false;
deferred.reject();
}
})
.error(function(data, status, headers, config) {
user = false;
deferred.reject();
});
// return promise object
return deferred.promise;
}
function getusername() {
return username;
}
My controller looks like this:
angular.module('smApp').controller('rootloginController', ['$scope', '$location', 'notificationFactory', 'AuthService',
function($scope, $location, notificationFactory, AuthService) {
$scope.submit = function() {
AuthService.login($scope.rEmail, $scope.rootPassword, 'root')
if (AuthService.isLoggedIn()) {
$location.url('/dashboard');
notificationFactory.success('Logged in as ' + rootEmail);
} else {
//ngNotifier.notifyError($scope.rEmail);
notificationFactory.error('Invalid username & password combination');
}
};
};
}]);
I am calling my getusername() in the if statementright after login() and since login has $http post it's asynchronous and I think im hitting a wall here.
So my main problem here is the first click always gives me error message and the second clicks logs me in. I am assuming this has to do with the promise not being fulfilled right away and taking some time to execute. I was wondering if there was anyway around this? I really dont have any other code to execute beside wait since this is a login page and using a timeout doesnt seem like the proper way to do it.
In this case you need to use the Promise API. Calls to the server made via the $http service return a promise, which allow binding of .success and .error methods.
The .then method may be used as a shorthand for both .success and .error. It accepts two functions that it executes in success and error scenarios respectively. Returning a promise in those functions allows chaining calls to the server.
In most cases, this should suffice:
// In service
login: function () {
return $http.post('your:url').then( // `then` here is optional, but possible
function () {}, // update service values without having to involve the controller (and/or transform the response object)
function () {} // throw error log mesages
)
}
// In controller
$scope.submit = function () {
AuthService.login().then(
function () {
// success logic: redirect, assign scope variables, etc
},
function () {
// error logic: allow retry
}
);
}
You have to call AuthService.isLoggedIn() after the login request has been completed. For this, first return the promise of the deferred object you created.
function login(uname, upwd, utype) {
// create a new instance of deferred
var deferred = $q.defer();
$http({
method: 'POST',
...
return deferred.promise;
}
Now, you can wait for the request to complete.
AuthService.login($scope.rEmail, $scope.rootPassword, 'root').finally(function() {
if (AuthService.isLoggedIn()) {
$location.url('/dashboard');
notificationFactory.success('Logged in as ' + rootEmail);
} else {
//ngNotifier.notifyError($scope.rEmail);
notificationFactory.error('Invalid username & password combination');
}
});

Angular Promise Response Checking

I am doing some http calls in Angular and trying to call a different service function if an error occurs. However, regardless of my original service call function return, the promise it returns is always "undefined". Here is some code to give context:
srvc.sendApplicantsToSR = function (applicant) {
var applicantURL = {snip};
var promise = $http({
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
method: 'POST',
url: applicantURL,
data: applicant
})
.success(function (data) {
return data;
})
.error(function (error) {
return [];
});
return promise;
};
Then, in the controller:
for (var applicant in $scope.applicants) {
$scope.sendATSError($scope.sendApplicantsToSR($scope.applicants[applicant]), applicant);
}
$scope.sendATSError = function (errorCheck, applicantNumber) {
if (angular.isUndefined(errorCheck)) {
console.log(errorCheck);
AtsintegrationsService.applicantErrorHandling($scope.applicants[applicantNumber].dataset.atsApplicantID);
}
};
However, it is always sending errors because every response is undefined. How can I differentiate between the two returns properly? Thank you!
Looking at angular documentation, the sample code is
$http({
method: 'GET',
url: '/someUrl'
}).then(function successCallback(response) {
// this callback will be called asynchronously
// when the response is available
}, function errorCallback(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
based on that - your first code snippet should be
srvc.sendApplicantsToSR = function(applicant) {
var applicantURL = {
snip
};
return $http({
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
method: 'POST',
url: applicantURL,
data: applicant
});
};
As others have said, $http's .success() and .error() are deprecated in favour of .then().
But you don't actually need to chain .then() in .sendApplicantsToSR() as you don't need (ever) to process the successfully delivered data or to handle (at that point) the unsuccessful error.
$scope.sendApplicantsToSR = function (applicant) {
var applicantURL = {snip};
return $http({
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
method: 'POST',
url: applicantURL,
data: applicant
});
};
Now, in the caller (your line of code in the for loop), a promise is returned (not data) and that promise will, on settling, go down its success path or its error path. Exactly what happens on these paths is determined entirely by the callback functions you write in one or more chained .thens .
So what you need to write is a kind of inside-out version of what's in the question - with $scope.sendApplicantsToSR() on the outside and $scope.sendATSError() on the inside - and linked together with a .then().
for (var prop in $scope.applicants) {
var applicant = $scope.applicants[prop];
$scope.sendApplicantsToSR(applicant).then(null, $scope.sendATSError.bind(null, applicant));
}
// Above, `null` means do nothing on success, and
// `function(e) {...}` causes the error to be handled appropriately.
// This is the branching you seek!!
And by passing applicant, the error handler, $scope.sendATSError() will simplify to :
$scope.sendATSError = function (applicant) {
return AtsintegrationsService.applicantErrorHandling(applicant.dataset.atsApplicantID); // `return` is potentially important.
};
The only other thing you might want to know is when all the promises have settled but that's best addressed in another question.
You should return your promisse to be handled by the controller itself.
Simplifying:
.factory('myFactory', function() {
return $http.post(...);
})
.controller('ctrl', function(){
myFactory()
.success(function(data){
// this is your data
})
})
Working example:
angular.module('myApp',[])
.factory('myName', function($q, $timeout) {
return function() {
var deferred = $q.defer();
$timeout(function() {
deferred.resolve('Foo');
}, 2000);
return deferred.promise;
}
})
.controller('ctrl', function($scope, myName) {
$scope.greeting = 'Waiting info.';
myName().then(function(data) {
$scope.greeting = 'Hello '+data+'!';
});
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="ctrl">
{{greeting}}!
</div>

Where should I put my response handler code for AJAX calls in Angular.js?

I'm fairly new to Angular.js. I try to keep my code organized, in particular, my controllers thin. My current setup is as follows:
User events get picked up by my controller, e.g. a button click will invoke $scope.upgrade() in controller.
$scope.upgrade = function() {
$scope.submitting = true // disable upgrade button
payment.chargeCreditCard($scope);
};
Then the payment service takes over and does the actual work which involves making AJAX call to backend. In my payment service module, the code is something like:
payment.chargeCreditCard = function($scope) {
$http({
method: 'post',
url: endpoint,
data: data
}).success(function() {
// do things...
}).error(function() {
$scope.error = "Something wrong happened."; // $scope was passed in from controller
$scope.submitting = false; // Re-enable the button
});
};
My question is, it feels wrong to pass $scope from controller to service. Services should be independent and should not care about things like setting the value of $scope.error and $scope.submitting.
What is the Angular way of doing this?
== UPDATE ==
So one solution I came up with is make the AJAX call in the service but return the result to controller. Something like:
// in controller
$scope.upgrade = function() {
// call service...
var response = payment.chargeCreditCard($scope);
response
.success(function() {
// ...
})
.error(function() {
// set $scope.error etc...
});
};
But then the controller is still pretty fat and the only thing that's in service is just the AJAX call itself which makes it seem not very worthwhile to separate the code.
Return the result of the $http call in the service and use the success handler in your controller.
Controller
$scope.upgrade = function() {
$scope.submitting = true // disable upgrade button
payment.chargeCreditCard().success(function() {
// do stuff
}).error(function() {
// Do other stuff
});
};
Service
payment.chargeCreditCard = function() {
return $http({
method: 'post',
url: endpoint,
data: data
});
};
Another way of doing it if this is not what you are after is using events. So in your service have the $rootScope has dependencies and use the $broadcast method.
Controller
$scope.upgrade = function() {
$scope.submitting = true // disable upgrade button
payment.chargeCreditCard();
$scope.$on("chargeCreditCard:success", function(ev, data) {
// Do stuff
});
$scope.$on("chargeCreditCard:error", function(ev, err) {
// Do other stuff
});
};
Service
payment.chargeCreditCard = function() {
return $http({
method: 'post',
url: endpoint,
data: data
}).success(function(data) {
$rootScope.$broadcast("chargeCreditCard:success", data);
}).error(function(err) {
$rootScope.$broadcast("chargeCreditCard:error", err);
});
};
But I guess the controller is as "fat", also not sure what is wrong with the controller containing the code that affect the property of its scope. By passing the scope you are making the service dependent on the controller or at least a set of properties on a scope/object which kind of go against the separation of concern.
The role of the service in this case would be to get the data and return it, one way or the other, but the service role should not be to modify controller properties.

multiple $http with promise using factory, angular

I am attempting to call multiple $http request in a factory which I Am using to flush out multiple inputs with data and set default selections. I Want to then call a promise once all 3 of these are done to call in the real form data (if there is any, sometimes there will not be in which case it will do nothing) to overwrite the defaults in place.
So here is my attempt at this -
the factory
I set up a factory to call all 3 inputs and their defaults (I am being sent them individually, i cannot change this for now). It looks like this:
.factory("getDefaults", function() {
return {
instructions: function() {
$http({
method: "GET",
url: "/getStringDropdown/materials"
})
.success(function(data, status, headers, config){
$scope.instructions.materials = data.text;
});
$http({
method: "GET",
url: "/getStringDropdown/reinforce"
})
.success(function(data, status, headers, config){
$scope.reinforce = data.options;
$scope.instructions.reinforce = $scope.reinforce[data.default];
});
$http({
method: "GET",
url: "/getStringDropdown/procedure"
})
.success(function(data, status, headers, config){
$scope.procedures = data.options;
$scope.instructions.procedures = $scope.procedures[data.default];
});
//return data here?
}
};
})
My question here is - do i have to return the data here? And also can I define the scopes here that are being used (as apposed to in the actual controller). I'm pretty sure this is wrong but I cant' find a good example of how to structure something like this properly.
The call in the contoller
So i the controller my thinking is i would then try something like this -
getDefaults.instructions().then(function(){
//possible return data here
//AFTER data is flushed out call farm data
$http({
method: "GET",
url: "/getSavedFormData/formID"
})
.success(function(data, status, headers, config){
$scope.instructions.materials= data.materials;
$scope.instructions.procedures = $scope.procedures[data.procedure];
$scope.instructions.reinforce = $scope.reinfoce[data.reinforcement];
});
});
So big picture - I am trying to get these 3 calls to run and populate and THEN the second one. I'm not sure what might or might not be the best approach, factory seemed to make sense based on the trying to consolidate the 3 calls into 1 place with a promise when they are all done. I'm thinking i need to return the data, but it would be pretty nice if i could define the scopes for the controller in the factory. I am still getting my bearing with angular so any/all guidance would be much appreciated. Thanks for reading!!
Your service is not aware of your $scope out of the box, nor do you probably want it to be as the whole point of services is to assist with the modularity of your code.
What you probably want to do is actually return the $http promise from your service so that your .success() callback can actually set models on the $scope via closure (being inside the controller).
So your factory would be more like this:
.factory("getDefaults", function() {
return {
instructions: $http({ method: "GET", url: "/getStringDropdown/materials" })
}
});
If you really think you'll never need those http calls separately and you only care about when they all resolve. You could return a $q.all() promise that will resolve when they all resolve:
.factory("getDefaults", function($http, $q) {
var promise1 = $http({ method: "GET", url: "/getStringDropdown/materials" });
var promise2 = $http({ method: "GET", url: "/getStringDropdown/materials" });
var promise3 = $http({ method: "GET", url: "/getStringDropdown/materials" });
return {
data: $q.all([promise1,promise2,promise3]),
anotherCall: $http.get('/anothercallUrl')
}
});
So now from your controller you could just do:
function myCtrl($scope,getDefaults){
getDefaults.data.then(function(data){
//access all three of your promises, their data, and set $scope models here
getDefaults.anotherCall.success(function(data){
//or another http call
});
};
}
$q.all documentation here: https://docs.angularjs.org/api/ng/service/$q
Using scopes in factories is not a good idea , my suggestion to not use, about multiple http calls you should use $q.all
example here in fiddle
for each call you should create defered object push it into array then use
$q.all(deferesArray).then(
function(resp){
// all requests are resolver , resp is array with resolved datas
}
)
You may want to have a look at angular's q$ (https://docs.angularjs.org/api/ng/service/$q)
1.) Create the first three promises that must finish "first"
var deferred = $q.defer();
$http({
method: "GET",
url: "/getStringDropdown/procedure"
})
.success(function(data, status, headers, config){
deferred.resolve(data);
});
var promise1 = deferred.promise;
2.) Use all method of q$, then call a "then" to the result to execute the second part of your logic
q$.all([promise1, promise2, promise3])
.then(function(results) {
$http({
method: "GET",
url: "/getSavedFormData/formID"
})
.success(function(data, status, headers, config){
$scope.instructions.materials= data.materials;
$scope.instructions.procedures = $scope.procedures[data.procedure];
$scope.instructions.reinforce = $scope.reinfoce[data.reinforcement];
});
});

Categories

Resources