Return error to Angular from Express - angularjs

I have a application where users can add movies to their watchlist. I want to prevent users from adding a movie that already exists in their watchlist.
This my addMovie function in my Angular controller:
$scope.addMovie = function (movie) {
movieFactory.selectMovie(movie).then(function(response){
movieFactory.addMovie(response);
Notification.success(movie.title + ' has been added to your watchlist');
$scope.movies = [];
$scope.overlay = false;
$scope.searchquery = '';
$rootScope.$broadcast('onAddMovieEvent', response);
});
};
I pass the movie object through from my initial ng-click. Then I request a selectMvoie function in the movieFactory that gets the correct movie data from the TMDB api.
Then I call the addMovie function in the movieFactory factory with the response from selectMovie function.
factory.addMovie = function (movie) {
var deferred = $q.defer();
$http({
method: 'POST',
url: '/movies',
data: movie
})
.success(function (data) {
deferred.resolve(data);
})
.catch(function () {
deferred.reject();
});
return deferred.promise;
};
This does a /post request which goes into my Express route:
router.post('/', function(req,res){
pool.getConnection(function(err, connection) {
connection.query('SELECT * FROM user_movieid WHERE movie_id= ? AND userid= ?' ,[req.body.id, req.user.id] , function(err, result) {
for (var i = result.length - 1; i >= 0; i--) {
if (movie.id === result[i].movie_id) {
console.log('exists');
}
}
});
});
})
There's also a connection.query that posts the movie into the database but that's not relevant now.
My situation now is that when I post a movie that already exists my node console shows the message exists but still posts the movie (obviously).
How would I return an "error" status back to my addMovie function in my Angular controller so I can do a if statement to show a different notification?

If you want to keep your API 'restful' then I would return a response with a non 2xx status code. I would think 409 Conflict is appropriate for this case.
router.post('/', function(req,res){
pool.getConnection(function(err, connection) {
connection.query('SELECT * FROM user_movieid WHERE movie_id= ? AND userid= ?' ,[req.body.id, req.user.id] , function(err, result) {
for (var i = result.length - 1; i >= 0; i--) {
if (movie.id === result[i].movie_id) {
console.log('exists');
// choose an appropriate status code, probably a conflict 409 in this case
return res.status(409).send('Movie already exists in your watchlist')
}
}
});
});
})
Then in your Angular code you can notify the user that the movie is already in their list.
$scope.addMovie = function (movie) {
movieFactory.selectMovie(movie).then(function(response){
movieFactory.addMovie(response);
Notification.success(movie.title + ' has been added to your watchlist');
$scope.movies = [];
$scope.overlay = false;
$scope.searchquery = '';
$rootScope.$broadcast('onAddMovieEvent', response);
})
// catch any errors
.catch(function() {
// ideally you should check the status code here and probably handle other non 409 status codes differently
// since this .catch will execute for any other 4xx and 5xx errors
Notification.fail(movie.title + ' is already in your watchlist');
});
};

Related

Wait for forEach to complete before returning value - AngularJS

Overview:
I'm creating an app using ionic framework and AngularJS. The App has 2 forms where user can upload images. In first form, image upload field allows to upload only one image and second form image upload field allows to upload more than one images. After user uploads images I'm showing preview to user right below image field. Then user click on "Save", which calls Web Service (defined into factory) to upload images to site. The Service function implements $q, so that form submission can continue only after uploading images. This is working fine.
Problem:
When uploading multiple images, I'm calling another helper function in controller which loop (forEach) over file data and call file save function for each file. But the execution in this helper function doesn't wait for forEach loop to complete. This is allowing me to get first file details of file saved, but not remaining saved files details.
Here is code I'm using for uploading file:
.controller('AppCtrl', function($q) {
// Upload single file.
$scope.fileUpload = function(fileData) {
var deferred = $q.defer();
if (angular.isUndefined(fileData) || angular.isUndefined(fileData.dataURL)) {
deferred.resolve("No new upload found.");
}
else {
var filedata = {
"file" : {
file: (fileData.dataURL).replace(/^data:image\/[A-Za-z]{3,4};base64,+/g, ''),
filename: fileData.file.name,
},
};
AuthServiceContent.uploadNewFile(filedata).then(function(response) {
deferred.resolve(response);
}, function(response) {
deferred.reject(response);
});
}
return deferred.promise;
};
// Upload multiple files.
$scope.fileUploadMultiple = function(data) {
var deferred = $q.defer();
var fileUploadStatus = [];
if (angular.isUndefined(data) || angular.isUndefined(data[0])) {
deferred.reject("No new upload found.");
}
else {
angular.forEach(data, function(fileData, index) {
$scope.fileUpload(fileData).then(function(response) {
fileUploadStatus[index] = response;
}, function(response) {
});
});
}
return (!angular.isUndefined(fileUploadStatus) ? deferred.resolve(fileUploadStatus) : deferred.reject(fileUploadStatus));
};
$scope.createContent = function(formData) {
$scope.fileUploadMultiple(formData.image).then(function(response) {
if (angular.isObject(response)) {
angular.forEach(response, function(fileData, index) {
console.log(fileData);
formData.image.und = [{'fid' : fileData.fid}];
});
console.log(formData);
}
else {
}
}, function(err) {
console.log("updates failed!!!");
});
return;
};
})
.factory('AuthServiceContent', function($q, $http, DEFAULT) {
var service_content = {
uploadNewFile: function(fileData) {
var deferred = $q.defer();
$http.post(DEFAULT.serviceURL + 'file', JSON.stringify(fileData), {
headers : {
'Content-Type': 'application/json',
'Cookie': 'cookieData',
}
})
.success(function (data, status, headers, config) {
deferred.resolve(data);
})
.error(function (data, status, headers, config) {
deferred.reject(data);
});
return deferred.promise;
}
};
return service_content;
});
I've been trying this for more than 2 days and find similar issue, but its not working.
Updates:
I got this working by adding extra check in loop, here is updated function in controller to upload multiple images.
$scope.fileUploadMultiple = function(data) {
var deferred = $q.defer();
var fileUploadStatus = [];
if (angular.isUndefined(data) || angular.isUndefined(data[0])) {
deferred.reject("No new upload found.");
}
else {
var dataLength = (data.length - 1);
angular.forEach(data, function(fileData, index) {
$scope.fileUpload(fileData).then(function(response) {
fileUploadStatus[index] = response;
// Check if we are at last element. If yes, then return status
// Return deffered status on last element.
if (dataLength == index) {
deferred.resolve(fileUploadStatus);
}
}, function(response) {
// Check if we are at last element. If yes, then return status
// Return deffered status on last element.
if (dataLength == index) {
deferred.reject(fileUploadStatus);
}
});
});
}
return deferred.promise;
};
The problem is that you have only one promise and are resolving it as soon as the first call uploaded file returns its results.
Save all the promises in an array and then try $q.all:
var promises = [];
angular.forEach(data, function(fileData, index) {
promises.push($scope.fileUpload(fileData));
});
$q.all(promises).then(function(results) {
// loop through the results, one for each promise, and do what you need
})
or just return $q.all(promises) and let the application code handle the results.
The big problem with $q.all is that it just gives and error if ANY of the promises is rejected. If you still want to handle the results for each promise, I use an implementation of $q.allSettled (I think I use this implementation. That returns a response for each promise -- either success or failure -- with the error message or the returned data so that I can then handle the results of each promise separately.
Hope this helps
The updated code in the answer is not working. If the last file is the smallest, it fires the resolve immediately after it's been uploaded. I use instead a fileIsUplaoded counter.
$scope.fileUploadMultiple = function(data) {
var deferred = $q.defer();
var fileUploadStatus = [];
if (angular.isUndefined(data) || angular.isUndefined(data[0])) {
deferred.reject("No new upload found.");
}
else {
var uploadCount = data.length;
angular.forEach(data, function(fileData, index) {
$scope.fileUpload(fileData).then(function(response) {
// if a file is uploaded reduce the uploadCount by 1
uploadCount --;
fileUploadStatus[index] = response;
// Check if we are at last element. If yes, then return status
// Return deffered status on last element.
if (uploadCount == 0) {
deferred.resolve(fileUploadStatus);
}
}, function(response) {
// if a file is not uploaded reduce the uploadCount by 1
uploadCount --;
// Check if we are at last element. If yes, then return status
// Return deffered status on last element.
if (uploadCount == 0) {
deferred.reject(fileUploadStatus);
}
});
});
}
return deferred.promise;
};

How to avoid nested call in ionic/angular?

I'm totally new to ionic/angular, this is my code:
.controller('PostCtrl', function($scope, Posts, $cordovaSQLite, $http) {
$scope.getPosts = function() {
$http.get('http://localhost/postIds').then(function(resp) {
_.each(resp.data, function(id) {
var query = "SELECT id FROM posts WHERE id = ?";
$cordovaSQLite.execute(db, query, [id]).then(function(res) {
if(res.rows.length = 0) {
$http.get('http://localhost/post/' + id).then(function(resp) {
var post = resp.data;
var query = "INSERT INTO posts (postId, title, user, content) VALUES (?,?,?,?)";
$cordovaSQLite.execute(db, query, [post.id, post.title, post.user, post.content]).then(function(res) {
// success
}, function(err) {
console.log(err);
});
}, function(err) {
console.log(err);
});
}
}, function (err) {
console.error(err);
});
});
}, function(err) {
console.log(err);
});
}
})
what am I doing is
get all ids from server
if id doesnt exist in db(sqlite)
get post by id from server
insert post into db
It ends up deeply nested, ugly.
what is the ionic, angular way to do this?
As the others suggested the best option is to use promises so you don't have to nest statements like you're doing.
AngularJs uses $q promises:
A service that helps you run functions asynchronously, and use their
return values (or exceptions) when they are done processing.
On the internet there are tons of articles about promises and how to chain them.
Recently I found this article which explains the common mistakes with promises.
It's worth reading cause it goes deep into the topic.
In AngularJs you would create a promise using the $q service:
function doSomething() {
var deferred = $q.defer();
deferred.resolve({value: true});
return deferred.promise;
}
This bit of code returns a promise which is resolved - since there's no async operation - when it's called. It would return an object with a property value = true.
The cool thing about promises is the fact that you can chain them:
doSomething()
.then(function(result){
// result.value should be true.
return doSomething();
})
.then(function(result){
// result.value should be true.
// this is the result of the second call.
});
passing the result of the previous - resolved - promise.
If promises are rejected because of some exceptions:
deferred.reject({value: false});
you can trap the error and stop the execution in the chain:
doSomething()
.then(function(result){
// result.value should be true.
return doSomething();
})
.then(function(result){
// result.value should be true.
// this is the result of the second call.
})
.catch(function(reason){
// reason for failure.
});
Finally you can use the finally to do some cleanup or other things:
doSomething()
.then(function(result){
// result.value should be true.
return doSomething();
})
.then(function(result){
// result.value should be true.
// this is the result of the second call.
})
.catch(function(reason){
// reason for failure.
})
.finally(function(){
// it's going to be executed at the end of the chain, even in case of error trapped by the catch.
});
Things are not so simple, though. At the beginning you might find yourself spending a few hours debugging the code.
How would I fix your code ?
First of all I would create a function which fetch the ids calling the web api:
function fetchIds() {
console.log('Fetching Ids ...');
var deferred = $q.defer();
$http({
method: 'GET',
url: 'http://localhost/postIds',
params: {}
})
.success(function(data) {
deferred.resolve(data);
})
.error(function(data, status) {
deferred.reject(data);
});
return deferred.promise;
}
As you can see I've implemented the system described above.
$http already returns a promise but I wrapped it creating a new promise, anyway.
Then I would have to query the database to find the non existing ids (I didn't put my code in a loop as it is easier to get all the records in one call):
function queryForIds(ids) {
console.log('Querying for Ids ' + ids.toString() + ' ...');
var deferred = $q.defer();
var params = [];
for (var i = 0; i < ids.length; i++) {
params.push('?');
}
window.myDatabase.transaction(function(tx) {
tx.executeSql("SELECT * FROM posts WHERE postId IN (" + params.join(',') + ")", ids,
function(tx, results) {
deferred.resolve(results.rows);
},
function(tx, reason) {
deferred.reject(reason);
});
});
return deferred.promise;
}
My code is going to be slightly different from your as I've used WebSql cause I wanted to test it in the browser.
Now we need to find the ids which do not exist in the db:
function getNonExistingIds(ids, dbData) {
console.log('Checking if Ids ' + ids.toString() + ' exist in the db ...');
if (!ids || ids.length === 0) {
console.log('No ids');
return [];
}
if (!dbData || dbData.length === 0) {
console.log('database is empty');
return ids;
}
var dbIds = [];
angular.forEach(dbData, function(data, key) {
dbIds.push(data.postId);
});
var nonExisting = [];
angular.forEach(ids, function(id, key) {
var found = $filter('filter')(dbIds, id, true);
if (found.length === 0) {
nonExisting.push(id);
}
});
return nonExisting;
}
This function does not return a promise but you still can pipe it like you would do with a real promise (You'll find out how later).
Now we need to call the web api to fetch the posts for the ids which couldn't be found in the database:
function fetchNonExisting(ids) {
if (!ids || ids.length === 0) {
console.log('No posts to fetch!');
return;
}
console.log('Fetching non existing posts by id: ' + ids.toString() + ' ...');
var promises = [];
angular.forEach(ids, function(id, key) {
var promise = $http({
method: 'GET',
url: 'http://localhost/post/' + id,
params: {}
});
promises.push(promise);
});
return $q.all(promises);
}
Things here get interesting.
Since I want this function to return one and only result with an array of posts I've created an array of promises.
The $http service already returns a promise. I push it in an array.
At the end I try to resolve the array of promises with $q.all. Really cool!
Now we need to write the posts fetched in the database.
function writePosts(posts) {
if (!posts || posts.length === 0)
{
console.log('No posts to write to database!');
return false;
}
console.log('Writing posts ...');
var promises = [];
angular.forEach(posts, function(post, key) {
promises.push(writePost(post.data));
});
return $q.all(promises);
}
Again, we are chaining an array of promises so that we can resolve them all in one go.
This function up here calls writePost:
function writePost(post) {
return $q(function(resolve, reject) {
window.myDatabase.transaction(function(tx) {
tx.executeSql("INSERT INTO posts (postId, title, user, content) VALUES (?,?,?,?)", [post.id, post.title, post.user, post.content],
function(tx, result) {
console.log('INSERT result: ' + result);
resolve(result);
},
function(tx, reason) {
console.log('INSERT failure: ' + reason);
reject(reason);
});
});
});
}
this bit here is quite complicated cause WebSql doesn't work with promises and I want them to be resolve in one go and get the result back.
Now what can you do with all these functions? Well, you can chain them as I explained earlier:
var ids = [];
fetchIds()
.then(function(data) {
console.log(data);
ids = data;
return queryForIds(data);
})
.then(function(dbData) {
return getNonExistingIds(ids, dbData);
})
.then(function(nonExistingIds) {
console.log('Non existing ids: ' + nonExistingIds);
return fetchNonExisting(nonExistingIds);
})
.then(function(response) {
return writePosts(response);
})
.then(function(result) {
console.log('final result: ' + result);
})
.catch(function(reason) {
console.log('pipe error: ' + reason);
})
.finally(function() {
// Always Executed.
});
The final result can find found in this gist.
If you prefer to download the whole application and test it on your PC, this is the link (myApp).

CordovaHttp and Angular Post - no response

I'm using CordovaHTTP with Angular and injected the Cordova HTTP into a service. I haven't found many examples on how to implement a POST so below is what I did so far. The issue I'm having is that the post block never reaches the success or error blocks and my debug statements are not getting printed.
Does this look correct?
Calling function:
this.authenticate = function ( code, data, callback ) {
try {
// Build url
var url = o.buildServerUrl(o.loginUrl, code);
RestService.post(url, data, function(response) {
if (response.status === o.HTTP_STATUS_OK) {
...
}
callback(response);
});
}
catch(err) {
var response = o.createServerErrorResponse(o.MSG_SERVER_ERROR);
callback(response);
}
}
Service:
oApp.service( 'RestService', function( cordovaHTTP ) {
this.post = function ( url, data, callback ) {
try {
// Build url
if (o.debug) console.log('Cordova REST: '+url);
cordovaHTTP.post( url, data, {}, function(response) {
if (o.debug) console.log('Rest ok');
// Success
var response = o.processServerResponse(response);
callback(response);
}, function(response) {
if (o.debug) console.log('Response error');
var response = o.processCordovaServerResponse(response);
callback(response);
});
}
catch(err) {
var response = o.createExceptionResponse(err.message);
callback(response);
}
}
});

AngularJS Controller function - TypeError: object is not a function

I am creating an email application that makes RESTful calls to Google APIs. I am using AngularJS and Java. I have had some success so far but I am unable to delete an email because I keep getting this error: TypeError: object is not a function.
My Angular knowledge is limited.
In my html I call the function deleteEmail and pass an email id.
Here is the controller:
app.controller('InboxController', function($rootScope, $scope, $cookies,
$location, InboxService) {
$rootScope.loggedIn = true;
$scope.emails = InboxService.getMessages().success(function(jsonData) {
$scope.emails = jsonData;
});
$scope.deleteEmail = function(id) {
$scope.id = {
'id' : id
};
// Parse to JSON
var responseJSON = angular.toJson($scope.id);
// Make call to InboxService
var response = InboxService().del(responseJSON).success(
function(jsonData) {
response = jsonData;
if (response == 'success') {
alert('Message deleted');
} else {
alert('Message not deleted');
}
});
}
});
The method $scope.emails works fine. It is the $scope.deleteEmail that is giving the error.
Here is the service:
app.factory('InboxService', function InboxService($http) {
var exports = {};
// Get a list of all emails
exports.getMessages = function() {
return $http.get('resources/inbox/get').error(function(data) {
console.log('There was an error!', data);
});
};
// Delete an email
exports.del = function(id) {
console.log('id ' + id);
return $http({
method : 'POST',
url : 'resources/inbox/delete',
data : id,
headers : {
'Content-Type' : 'application/json'
}
});
};
return exports;
});
I don't think I am getting as far as the service though. The problem seems to be with the controller.
Console output:
TypeError: object is not a function
at Scope.$scope.deleteEmail (http://localhost:8080/NewProject/js/controllers.js:64:18)
at Parser.functionCall (http://localhost:8080/NewProject/bower_components/angular/angular.js:10903:21)
at ngEventDirectives.(anonymous function).compile.element.on.callback (http://localhost:8080/NewProject/bower_components/angular/angular.js:19259:17)
at Scope.$get.Scope.$eval (http://localhost:8080/NewProject/bower_components/angular/angular.js:12811:28)
at Scope.$get.Scope.$apply (http://localhost:8080/NewProject/bower_components/angular/angular.js:12909:23)
at HTMLButtonElement.<anonymous> (http://localhost:8080/NewProject/bower_components/angular/angular.js:19264:23)
at http://localhost:8080/NewProject/bower_components/angular/angular.js:2853:10
Pls be sure you called deleteEmail(id) from within html with right syntax without $scope
I got it working. I changed the Controller delete method to this:
$scope.delete = function (id) {
InboxService.delete(id).success(function() {
$scope.loadInbox();
});
};
And the Service method to this:
// Delete an email
exports.delete = function(id) {
console.log('id ' + id);
return $http({
method : 'DELETE',
url : 'resources/inbox/delete',
data: id,
headers : {
'Content-Type' : 'application/json'
}
});
}

How to render errors to client? AngularJS/WebApi ModelState

I'm building an AngularJS SPA application with WebApi for the backend. I am using attributes for model validation on the server, if validation fails this is what I return from the ModelState.
{"Message":"The request is invalid.","ModelState":{"model.LastName":["Last Name must be at least 2 characters long."]}}
How do I then render this to the client with AngularJS?
//Save User Info
$scope.processDriverForm = function(isValid) {
if (isValid) {
//set button disabled, icon, text
$scope.locked = true;
$scope.icon = 'fa fa-spinner fa-spin';
$scope.buttonText = 'Saving...';
$scope.submitted = true;
$scope.formData.birthDate = $scope.formData.birthMonth + '/' + $scope.formData.birthDay + '/' + $scope.formData.birthYear;
$http({
method: 'POST',
url: 'api/Account/Register',
data: $.param($scope.formData),
headers: { 'Content-Type': 'application/x-www-form-urlencoded' } // set the headers so angular passing info as form data (not request payload)
})
.success(function (data) {
console.log(data);
toastr.success('User ' + $scope.formData.username + ' created!');
$scope.userForm.$setPristine();
$scope.formData = {};
//reset the button
$scope.locked = false;
$scope.icon = '';
$scope.buttonText = 'Save';
//reset validation submitted
$scope.submitted = false;
})
.error(function (data, response) {
console.log(data);
toastr.error('Ooops! There was an error creating the user. Try again and if the problem persists, contact Support.');
//reset the button
$scope.locked = false;
$scope.icon = '';
$scope.buttonText = 'Save';
$scope.submitted = false;
var resp = {};
var errors = [];
for (var key in resp.ModelState) {
for (var i = 0; i < resp.ModelState[key].length; i++) {
errors.push(resp.ModelState[key][i]);
}
}
$scope.errors = errors;
});
}
else {
toastr.warning('Invalid User Form, correct errors and try again.');
}
};
When making your call to your server, capture the error based upon the rejection of the $http promise.
Then in your controller I would suggest flattening the response to an array of errors upon handling of the error for display as shown in this fiddle example:
for (var key in resp.ModelState) {
for (var i = 0; i < resp.ModelState[key].length; i++) {
errors.push(resp.ModelState[key][i]);
}
}
To put it all together:
// Post the data to the web api/service
$http.post(url, data)
.success(successHandler)
.error(function (response) {
// when there's an error, parse the error
// and set it to the scope (for binding)
$scope.errors = parseErrors(response);
});
//separate method for parsing errors into a single flat array
function parseErrors(response) {
var errors = [];
for (var key in response.ModelState) {
for (var i = 0; i < response.ModelState[key].length; i++) {
errors.push(response.ModelState[key][i]);
}
}
return errors;
}
The simplest way might be to grab all the errors from ModelState and put them into a new property on $scope.
$http.post(url, data).
success(successHandler).
error(function (response) {
$scope.errors = getErrors(response);
});
function getErrors(responseWithModelState) {
var errors = [];
/*
Get error messages out of ModelState property, and push them into the errors variable...
Brocco beat me to it. :-)
*/
return errors;
};
Then in your HTML...
<ul>
<li ng-repeat="e in errors">{{e}}</li>
</ul>
Or, instead of doing this in every error handler, you could write it once and have it apply to every HTTP request by using an interceptor. I've never written one myself, so I'll just point you to the doc (scroll down to the Interceptors section).

Resources