We are making an HTTP request based upon user input. So each time the user inputs a character, an HTTP request is kicked off. The issue we are having: the user will enter ABC and then enter ABCD. ABCD will respond before ABC. Thus, the displayed results do not match ABCD. They match ABC.
Below is our current solution. We are rejecting all promises that have not been resolved. This seems to me to be an anti-pattern. Is there an approach to this that will result in better performance, readability, or better functionality?
function cancelPreviousSearchRequest(previousSearchRequest) {
if(previousSearchRequest) {
previousSearchRequest._defer.resolve();
}
}
function getPVendors(typed, subTypes) {
var pVendorsDefer = $q.defer();
var pVendorsPromise = pVendorsDefer.promise;
$http.get(SERVLET_CONTEXT + RestConstants.masterDataIntegrationSearchByPvendorName(typed, subTypes), {timeout: pVendorsPromise})
.success(function(data){
var result = _.get(data, '_embedded.paymentVendors', []);
pVendorsDefer.resolve(result);
})
.error(function(data){
if(data != null) {
growl.error("Service error occurred while retrieving Payment Vendors");
}
pVendorsDefer.reject();
});
pVendorsPromise._defer = pVendorsDefer;
return pVendorsPromise;
}
NOTES FROM COMMENTS:
We are doing a debounce already: ng-model-options="{ debounce: 150 }"
Related
I am trying to see what would be the best way to approach this. I am using MVC .Net Core Web App.
When a user clicks a "Create Ticket" button, it checks to see how many tickets are open. If more than 5 tickets are open, then display toast alert message. If not, then create ticket.
public IActionResult Create()
{
var ticket = _context.tickets
.where(x => x.statusID == "1") //1 = open
if(ticket.Count() > 5){
//from my research many people use tempData here
TempData["Alert"] = "You have exceeded limit"
return ? //What do I return???
}
Ticket ticket = new Ticket();
_context.ticket.Add(ticket);
_context.ticket.SaveChanges();
return RedirectToAction("Index");
}
I want to display the alert without refreshing the page. Will the best approach be to do an Ajax call when button is clicked? If so, would it look like this
$(function () {
$("#Createbtn").click(function (e) {
e.preventDefault();
$.ajax({
cache: false,
type: "GET",
url: "/Tickets/CreateValidation",
datatype: "json",
success: function (data) {
alert(data);
},
})
})
})
Then from the action I can redirect to "Create" action?
I appreciate the input. Thanks.
As far as I know, if you want to return message to the client ajax success function, you should return OKresult in your controller method.
Besides, since you use ajax to call the method function, it will return the index html makeup instead of redirect to the index method when you call RedirectToAction.
In my opinion, you should check the message in the client success function. If the return message is a url, you should use window.location.href to redirect to the returned url.
Details, you could refer to below codes:
[Route("Tickets/CreateValidation")]
public IActionResult Create()
{
// var ticket = _context.tickets
// .where(x => x.statusID == "1") //1 = open
//if (ticket.Count() > 5)
// {
// //from my research many people use tempData here
// TempData["Alert"] = "You have exceeded limit"
// return ? //What do I return???
// }
// Ticket ticket = new Ticket();
// _context.ticket.Add(ticket);
// _context.ticket.SaveChanges();
// return RedirectToAction("Index");
//I don't have your dbcontext, so I create a ticket number directly
var ticketnumber = 4;
if (ticketnumber >5)
{
//If we want to return string to the client side inside a post action method, we could directly using OK or Json as the result.
//If we use OK to return data, the we could receive this data inside the ajax success method
return Ok("You have exceeded limit");
}
return Ok("/Default/Index");
}
Client-side ajax:
$(function () {
$("#Createbtn").click(function (e) {
e.preventDefault();
$.ajax({
cache: false,
type: "GET",
url: "/Tickets/CreateValidation",
datatype: "json",
success: function (data) {
if (data == "/Default/Index") {
window.location.href = data;
} else {
alert(data);
}
}
})
})
})
</script>
Result:
If the ticket number > 5:
If the ticket number <5:
You could find the page has refreshed.
I have a table made from a JSON, there is a button on every row and when you click it invokes a web service passing the element id. The web services tryes to delete the element from the database, if it can be deleted returns a simple JSON with the attributte 'status' and two possible values 'ERASED' (if deleted) or 'IN_USE' (if not). When I click on the button the web service answers ok ('IN_USE' actually). But when I try to read the 'status' value for show a message (depending if it's 'IN_USE' or 'ERASED') it returns me 'undefined' and don't know why. I've been making some tricks but still don't work. I also did the web service with slim framework 2.
The controller (just the function with troubles):
$scope.show = function (id) {
$http.get('http://10.0.203.73/WS/ws.php/tipusactius/edita/elimina/' + id + '.json').success(function (data) {
$scope.sts = data.status;
$window.alert($scope.sts);
});
if ($scope.sts.status == 'IN_USE') {
$window.alert('Aquest atribut no es pot eliminar perque és en ús');
}
}
There is the web service (it's done with slim framework 2):
$app->get('/tipusactius/edita/elimina/:id.json', function($id){
header("Content-Type: application/json");
$SQL = 'DELETE FROM atributs_actiu WHERE idatributs_actiu = '.$id;
error_log('DELETE STATEMENT: '.$SQL, 0);
$mysqli = getDB();
$r = $mysqli->query($SQL);
error_log(mysqli_error($mysqli));
if(!$r){
$results[] = array( 'status' => 'IN_USE' );
}
else{
$results[] = array( 'status' => 'ERASED' );
}
echo json_encode($results);
});
The web services run ok, but I'm getting an undefined on the console when I try check the status value.
Solved:
There was two mistakes on this case:
1-The funciton is asynchronous, so even I get something from the server the message can be still being 'undefined'
2-I wasn't caching the 'status' value appropiettly.
This is how i did it finally:
$scope.sts[0].status
All this inside $http.get function as Marius Wirtherle said:
Your Problem is probably that the $http Request is executed
asynchronously. So the Request is not finished yet when you do the
$window.alert
Modify your code like this to use wait for the $http Promise to
Resolve:
$scope.show = function(id){
$http.get('http://10.0.203.73/WS/ws.php/tipusactius/edita/elimina/' +
id + '.json')
.then(function(response){ // success callback (.success is deprecated)
$scope.sts = response.data.status;
if ($scope.sts == 'IN_USE') {
$window.alert('Aquest atribut no es pot eliminar perque és en ús');
}
}, function(response){ //error callback
$window.alert(response.statusText);
});
}
Further reading on $http:
https://docs.angularjs.org/api/ng/service/$http
Your Problem is probably that the $http Request is executed asynchronously. So the Request is not finished yet when you do the $window.alert
Modify your code like this to use wait for the $http Promise to Resolve:
$scope.show = function(id){
$http.get('http://10.0.203.73/WS/ws.php/tipusactius/edita/elimina/' + id + '.json')
.then(function(response){ // success callback (.success is deprecated)
$scope.sts = response.data.status;
if ($scope.sts == 'IN_USE') {
$window.alert('Aquest atribut no es pot eliminar perque és en ús');
}
}, function(response){ //error callback
$window.alert(response.statusText);
});
}
Further reading on $http: https://docs.angularjs.org/api/ng/service/$http
(Also i think you have a .status to much in your code. One at line $scope.sts = data.status; and then in if($scope.sts.status == ...). So its basically data.status.status)
I am making $http request to multiple environment and processing after I get all the responses. I am using the code below:
$q.all(Object.keys($rootScope.envs).map(request)).then(function(res){
var results = {};
for (var env in res) {
results[env] = res[env].data;
}
}, function(err){
console.error(err);
});
function request(env) {
return $http.get(callService.getDomainUrl()+'/'+$rootScope.envs[env]+ '/hosts.json');
}
The above code works fine, but the results object looks like below:
{
0: {data:{}},
1: {data:{}},
2: {data:{}},
3: {data:{}}
}
I want the corresponding response for each key and the results should be like
{
env1: {data:{//data for env1}},
env2: {data:{//data for env2}},
env3: {data:{//data for env3}},
env4: {data:{//data for env4}},
}
How to map the corresponding response to the key? Please let me know how to get this as this is asynchronous request. Should I have something from the API to know which env the API is coming from?
I think the simplest way would be to push the result handling into the request function, that way you still have the 'env' value in scope.
var results = {};
$q.all(Object.keys($rootScope.envs).map(request)).then(function(res){
// Do something with 'results' here.
}, function(err){
console.error(err);
});
function request(env) {
return $http.get(callService.getDomainUrl()+'/'+$rootScope.envs[env]+ '/hosts.json')
.then(function(res) { results[env] = res.data; return env; });
}
Another option would be to replace my return env with return [env, res.data] and then you can go back to creating the results object as in your original code.
The important thing here is to remember you can handle the $http.get promises individually as well as using the promises from the call to then in $q.all.
I have the following email field for taking user input for email. Once the email has been entered and focus is taken away from the field ng-blur triggers the function that checks whether the email entered by the user has already been taken:
<input type="email" class="form-control" placeholder="" ng-model="email" name="email" required ng-blur="isFound(email)">
To show the error I've got the following span:
<span class="help-block error" ng-show="blurred && isTaken">Email is taken already</span>
And here is the function isFound(email):
$scope.isFound = function(email) {
service.isEmailTaken(email).then(function(data) {
console.log(data); //this shows either null or the returned data
$scope.blurred = true;
if(data == null) {
$scope.isTaken = false;
} else {
$scope.isTaken = true;
}
});
}
Now when I try an email that has been taken, it shows the message correctly. But after that even when I enter the email that has not been taken it keeps showing the same message that the Email is taken already.
Could somebody help me understand the reason why is it happening and how to resolve it?
EDIT - Adding AngularJS and NodeJS/ExpressJS/Mongoose code below:
AngularJS
factory.isEmailTaken = function(email) {
return $http({url: "/api/queries/email/" + email, method: "GET"}).then(function(resp) {
return resp.data;
});
};
ExpressJS/NodeJS/Mongoose
app.get('/api/queries/email/:email', function (req, res) {
Query.findOne({email: req.params.email}, function (err, query) {
if (err)
res.send(err);
res.json(query);
});
});
When the email was not taken already value of data was null, and the typeof data was String.
The reason why it was not working earlier was because if(data == null) was always turning out to be false.
if(data == 'null') got things working properly.
In my angular app I want to make changes to several locations in my firebase with a mix of transactions and set. I have written a promise chain with a little help. Now I need to handle any errors that may occur.
In the event of an error on any of the promises I would want to roll back any changes made in firebase (the successful promises) and alert the user to the failure.
Current code below
$scope.addNewPost = function() {
var refPosts = new Firebase(FBURL).child('/posts').push();
// Get tags into array for incrementing counters
var tags = $scope.post.tags.split(', ');
var allPromises = [];
// Iterate through tags and set promises for transactions to increment tag count
angular.forEach(tags, function(value, index){
var dfd = $q.defer();
var refTag = new Firebase(FBURL).child('/tags/' + value);
refTag.transaction( function (current_value) {
return current_value + 1;
}, function(error, committed, snapshot) {
if (committed) {
dfd.resolve( snapshot );
} else {
dfd.reject( error );
}
});
allPromises.push( dfd.promise );
});
// Add promise for setting the post data
var dfd = $q.defer();
refPosts.set( $scope.post, function (error) {
if (error) {
dfd.reject(error);
} else {
dfd.resolve('post recorded');
}
});
allPromises.push( dfd.promise );
$q.all( allPromises ).then(
function () {
$scope.reset(); // or redirect to post
},
function (error) {
// error handling goes here how would I
// roll back any data written to firebase
alert('Error: something went wrong your post has not been created.');
}
);
};
So what I need to know is how do I roll back any changes that happen to my firebase data in the event that one of these promises fail. There could be any number of updates happening in firebase. (for example: 3 tags being incremented via transaction and the post data being set)
How would I write the failure function to calculate what was successful and undo it? If this is this even possible.
--------------- sub question from original post has been solved ---------------
Also how do you force errors? I've tried setting a variable like below but it doesn't seem to work, is there something wrong with my .then?
refPosts.set( $scope.post, function (error) {
var forceError = true;
if (forceError) {
dfd.reject(forceError);
} else {
dfd.resolve('post recorded');
}
allPromises.push( dfd.promise );
});
There are two instances of this line, and they are both in the wrong place:
allPromises.push( dfd.promise );
In the first block, it should be in the last statement in the forEach callback, not in the transaction callback.
In the second block, it should be after the call to set(), not in the callback.
The way your code is written now, $q.all() is getting an empty array of promises. That could also be what's interfering with the forceError test you're attempting.