AngularJS: Cancel overwriting values $resource object after calling save (); - javascript

var User = $resource(
'http://test/index.php'
);
var user = User.get({id:'1'});
// GET: http://test/index.php?id=1
// server returns: { "login":"foo", "name":"bar", "mail":"baz" }
user.name = "qux";
user.$save();
// POST: http://test/index.php?id=1
// server returns: { "login":"foo", "name":"bar", "mail":"qux"}
In this case, when you call the save() user object, properties will be replaced by those that came from the server.
But if the server responds like this:
{
"errors":{
"login":"too short",
"name":"is already using that name.",
"mail":"invalid email."
}
}
User object properties are overwritten and instead, property errors containing these mistakes will come up.
Is there a way to change the behavior of $resource? I would like to check the status of the response and, based on that, decide whether to update the properties of an object or report an error to the user.

Angular's $resource is meant to interact with RESTful web services.
In RESTful web services, if there's an error while saving a resource, you should return an appropriate HTTP status (for example, 400).
Then, you can optionally use the error callback:
user.$save(function (response) {
console.log("success!");
}, function (response) {
console.log("error");
});
For a full list of error HTTP statuses:
http://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_Client_Error

Related

How should AngularJS handle 403 error in $http.post due to outdated XSRF token?

An AngularJS version 1.4.8 app is getting an unhandled 403 error when its login form sends data to a backend REST authentication service after the user's browser has been left open for many (16 in this case) hours. Upon deeper analysis, the root cause is that the client AngularJS app has outdated cookies for XSRF-TOKEN and JSESSIONID, which causes the backend Spring Security to reject the request to the public /login1 service because Spring thinks the request is cross site request forgery.
The problem can be resolved manually if the user closes all browser windows and then re-opens a new browser window before making the request again. But this is not an acceptable user experience. I have read the AngularJS documentation at this link, and I see that I can add an errorCallback function, but how specifically should i re-write the function to handle the 403 error?
Here is the original this.logForm() method in the authorization service, which you can see does not handle 403 errors:
this.logForm = function(isValid) {
if (isValid) {
var usercredentials = {type:"resultmessage", name: this.credentials.username, encpwd: this.credentials.password };
$http.post('/login1', usercredentials)
.then(
function(response, $cookies) {
if(response.data.content=='login1success'){// do some stuff
} else {// do other stuff
}
}
);
}
};
Here is my very rough attempt at a revised version of the this.logForm() method attempting to handle a 403 error following the example in the AngularJS documentation:
this.logForm = function(isValid) {
if (isValid) {
var usercredentials = {type:"resultmessage", name: this.credentials.username, encpwd: this.credentials.password };
$http({ method: 'POST', url: '/login1', usercredentials })
.then(
function successCallback(response, $cookies) {
// this callback will be called asynchronously when the response is available
if(response.data.content=='login1success'){// do some stuff
} else {// do other stuff
}
},
function errorCallback(response, status) {// is status a valid parameter to place here to get the error code?
// called asynchronously if an error occurs or server returns response with an error status.
if(status == 403){
this.clearCookies();
// try to call this POST method again, but how? And how avoid infinite loop?
}
}
);
}
};
What specific changes need to be made to the code above to handle the 403 error due to server-perceived XSRF-TOKEN and JSESSIONID issues? And how can the post be called a second time after deleting the cookies without leading to an infinite loop in the case where deleting the cookies does not resolve the 403 error?
I am also looking into global approaches to error handling, but there is a combination of public and secure backend REST services, which would need to be handled separately, leading to complexity. This login form is the first point of user entry, and I want to handle it separately before looking at global approaches which would retain a separate handling of the login form using methods developed in reply to this OP.
You could restructure your http calls to auto retry, and use promises in your controllers (or whatever)
var httpPostRetry = function(url, usercredentials) {
var promise = new Promise(function(resolve, reject) {
var retries = 0;
var postRetry = function(url, usercredentials) {
if (retries < 3) {
$http({ method: 'POST', url: '/login1', usercredentials })
.then(function(result) {
resolve(result);
}).catch(function(result) {
retries ++;
postRetry(url, usercredentials);
});
} else {
reject(result);
}
};
}.bind(this));
return promise;
}
and then you would call
httpPostRetry(bla, bla).then(function(result) {
// one of the 3 tries must of succeeded
}).catch(function(result) {
// tried 3 times and failed each time
});
To handle specific http errors you can broadcast that specific error and handle that case in a specific controller. Or use a service to encapsulate the status and have some other part of your code handle the UI flow for that error.
$rootScope.$broadcast('unauthorized http error', { somedata: {} });
Does this help?
Have a look at the angular-http-auth module and how things are done there. I think one key element you would want to use is a http interceptor.
For purposes of global error handling, authentication, or any kind of
synchronous or asynchronous pre-processing of request or
postprocessing of responses, it is desirable to be able to intercept
requests before they are handed to the server and responses before
they are handed over to the application code that initiated these
requests. The interceptors leverage the promise APIs to fulfill this
need for both synchronous and asynchronous pre-processing.
After playing around with interceptors you can look at the angular-http-auth http buffer and the way they handle rejected requests there. If their interceptor receives a responseError, they add the config object - which basically stores all information about your request - to a buffer, and then any time they want they can manipulate elements in that buffer. You could easily adept their code to manipulate the config's xsrfHeaderName, xsrfCookieName, or parameters on your behalf when you receive a 403.
I hope that helps a little.

How to get currently logged in user details from Promise object in jhipster based application

I am trying to create 'Order' object in the front end and pushing it into the database using REST services. POJO of 'Order' looks like below
#NotNull
#Field("total")
private BigDecimal total;
#Field("status")
private String status;
#Embedded
public User user;
Now I have a 'Principal' service which is providing information of the currently logged in user.I have tried 'console.log(Principal.identity())' which is returning result as shown below. Here 'User' data is present inside the '$$state' Object.
I am not able to find out how to take 'user' data from promise object and add to the 'Order' object. I have dirty method to get user data by digging inside the Promise object as shown below but I am skeptical about this method.
What is the correct way to get data from Promise in this scenario?
EDIT:
This is jhipster based application. Below is the "Principle" service code
'identity: function (force) {
var deferred = $q.defer();
if (force === true) {
_identity = undefined;
}
// check and see if we have retrieved the identity data from the server.
// if we have, reuse it by immediately resolving
if (angular.isDefined(_identity)) {
deferred.resolve(_identity);
return deferred.promise;
}
// retrieve the identity data from the server, update the identity object, and then resolve.
Account.get().$promise
.then(function (account) {
_identity = account.data;
_authenticated = true;
deferred.resolve(_identity);
Tracker.connect();
})
.catch(function() {
_identity = null;
_authenticated = false;
deferred.resolve(_identity);
});
return deferred.promise;
}'
Here is the jhipster generated method to receive resource from server using ngResource.
'angular.module('hotSpiceApp')
.factory('Order', function ($resource, DateUtils) {
return $resource('api/orders/:id', {}, {
'query': { method: 'GET', isArray: true},
'get': {
method: 'GET',
transformResponse: function (data) {
data = angular.fromJson(data);
return data;
}
},
'update': { method:'PUT' }
});
});'
The Principal.identity() function returns a Promise. First read what is promise.
Then do something like:
Principal.identity().then(function (user) {
var data = {
// other fields
user: user
};
// do something with data only inside this inner function
});
Doing Principal.identity().$$state.value is bad not only because it related on internal implementation of angular's promises but it also won't work in all cases. Promises by nature is asynchronous and this work only because Jhipster caches result of http request which returns current user. But if there are no info about current user at time of your new requst, then Principal.identity().$$state.value will be undefined because first it need to send http request to the server and only after that this promise will be "resolved" (internally it will set the value variable and call function defined in then method).
Also I should note, that you should not pass current user from JS code to the server. You should take current user on server side (from session or something) after request arrived and set it to your model if needed. Never trust user code (code which runs in user's browser). It can inject any value to this request and send it to the server.
Can you explain the way you are retrieving the principal service? If it is returning a promise you need to set the data you want to a local variable in the "then" method.
Now to see what "Principal" service has , you can use the below code in chrome console
angular.element(document.querySelector('html')).injector().get(Principal);

Cannot update Parse object via REST API

Right, so I'm simply trying to update my object via the REST API. My request succeeds, I get a 200 response back containing the latest updated timestamp, but the object's column value has not changed.
My Movies class has a title and a genre column, the rights on the class are set to public read write on all rows.
Here is some code
var data = {title:'The Revenant'};
qwest.put('https://api.parse.com/1/classes/Movies/myObjectId', JSON.stringify(data))
.then(function(xhr, response) {
console.log(response);
})
.catch(function(xhr, response, e) {
console.log(e);
});
The response I get back?
{"updatedAt":"2016-01-24T07:59:54.977Z"}
So the request succeeded but if I GET the object again or check in the Parse admin page, the object has not changed. What gives?
EDIT
FYI, if I use the Javascript SDK, I can update the model.
var Movies = Parse.Object.extend("Movies");
var query = new Parse.Query(Movies);
query.get(myObjectId, {
success: function (movie) {
movie.set("title", data.title);
movie.save();
},
error: function (object, error) {
console.log(error);
}
});
This updates the model. For my particular use case though, I would really prefer to use the REST API rather than the SDK, but I guess this means it is not a permissions issue or an id mismatch etc.,
code snippet
qwest.put('https://api.parse.com/1/classes/Movies/Dh7zjiP9KW', data, {dataType:"json",headers:{'x-parse-application-id':'XXX','X-Parse-REST- API-Key':'XXX'}})
.then(function(xhr, response) {
console.log(response);
})
.catch(function(xhr, response, e) {
console.log(e);
});
The response you're getting indicates that the object was updated successfully. Double check that you're looking at the correct object, and that the "updatedAt" field matches the response you saw earlier.
What happens if you fetch the object right away, using the same "qwest" client and https://api.parse.com/1/classes/Movies/myObjectId resource URL (with the correct object id)?
Try removing JSON.stringify(data) and just pass data,

Get tweets using angularjs

I am trying to make an app with material design and angularjs to get the tweets using hashtag search.
getTweets: function(hashtag, since,$http) {
var cfg = {};
var paramSince = since ? '&since_id='+ since : '';
var queryUrl = 'https://api.twitter.com/1.1/search/tweets.json?q=%23'+hashtag+paramSince;
// var queryUrl = '/search?hashtag='+hashtag+paramSince;
var promise = $http.get(queryUrl, cfg).then(function (response) {
return response;
});
return promise;
}
This API returns error 215, Bad Authentication Data
Here is the full application
STEPS TO REPRODUCE:
(i) Click Add Account
(ii)Login
(iii) Click finish
$http is undefined. You injected $http service into your twitterApp.services factory, then you (try) redeclared it inside the returned function getTweets.
In this case there is no "magic", you call getTweets with two arguments, so $http becomes undefined. The solution is removing this parameter from getTweets and use $http as a closure.
UPDATE:
There's no error handling in the process, you have to reject the promise when error occurs. This way you can also see the error comes from the server.
http://plnkr.co/edit/Lbb6EvwsjuecmFn5Vchd?p=preview
As you can see on the console, when trying to get connected, the server returns an origin error:
Error: Origin "http://run.plnkr.co/Of0F9UHpjhrqkjdw/" does not match
any registered domain/url on oauth.io(…)
It's probably about settings in your server (in this case, oauth.io) in terms of CORS.

Backbone sync error even after response code 200

Hello Backbone ninjas,
This is my first time using Backbone - so please excuse my "noob"ness.
In my functionality (part of a larger app),I have a Backbone View vA, backed by a model mA (as it should be ) and the server side is in Spring MVC having annotated Spring controller methods with #RequestBody and #ResponseBody. I've got Jackson working fine with Spring.
Now, in the app,
Backbone.Model
|_ BaseModel (custom base model for our app)
|_ mA (my model)
mA has its own endpoint and it Backbone sucessfully calls that when making a PUT request i.e., when I call save() from a submit button event handler from View vA like so:
this.model.save({
success : function(){
alert('Request submitted successfully');
},
error : function(){
alert('Something awful happened.');
}
});
Our BaseModel has the following:
define([], function() {
window.BaseModel = Backbone.Model.extend({
......
});
onSyncError : function(model, response) {
switch (response.status) {
case 403:
[...//some more code ]
default:
alert(bundle.getDefault('HTTP_RESP_OTH') + response.status);
}
},
onSyncSuccess : function(model, response) {
alert('Sync done! ');
},
sync : function(method, model, options) {
options.error = this.onSyncError;
Backbone.sync.call(this, method, model, options);
....//some more stuff.
},
}
Spring controller method:
#RequestMapping(value="/resource/xyz/{id}.json", method = RequestMethod.PUT, consumes = {"application/json"}
, produces = {"application/json"})
#ResponseBody
public Map<String,String> methodX(#RequestBody XyzDTO xyzDTO){
....
map.put("msg", "success");
return map;
}
Also, right before I make the save call, I modify a few Model attributes, since the server side DTO has a different structure like so:
this.model.unset("abc",{ silent: true });
this.model.set( { abc: {id : "2",xyz:{ ... //more code } );
The issue is, calling save() generates a PUT request and successfully calls the Spring endpoint handler, but I get a response code 200 (which is what I expect),
but when I trace the call with Firebug, it goes into the onSyncError method and gives me an error message (because of the "default" case in it).
The Backbone doc says : "When returning a JSON response, send down the attributes of the model that have been changed by the server, and need to be updated on the client". Well, I don't need to update the model on the client side, its one of the last screens and I just need to tell the user of a success / error and
redirect him to a main page/dashboard.
I read up some more, and it seems code 200 as response is not sufficient - there might be JSON parsing errors causing the sync to fail.
I checked the response in Firebug, and the response JSON looks like {"msg":"Success"}.
So, what could be going wrong?
Backbone.Model.save() expects the response from the server to be an updated hash of the model's values. If your response is of the kind {"msg":"Success"}, Backbone may fail to sync with your model. Basically, it interprets your HTTP 200 JSON response as the model's attributes and tries to sync the model accordingly.
You might try either 1) Making your Spring controller path return a JSON-ified model response, 2) Return a plain 200 with an empty response body or 3) write a custom parse method which looks for responses with the {"msg":"Success"} format and responds differently.
Thanks for your time. I was finally able to get around the problem by using $.ajax to make the PUT request, thereby bypassing the whole Backbone sync thingy. My success handler in the ajax callback handles the response and there are no more sync errors (since its not being called anyways) :)
I'll share my experience with the same problem;
custom base-model and
calling model.save and no success event fired.
My problem was with a custom set function in the base model which didnt return "this".
If you peek at the backbone source code for model-save you'll find this snippet:
options.success = function(resp) {
// Ensure attributes are restored during synchronous saves.
model.attributes = attributes;
var serverAttrs = model.parse(resp, options);
if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) {
return false;
}
if (success) success(model, resp, options);
model.trigger('sync', model, resp, options);
};
The !model.set(serverAttrs, options) failed in my case and the save-function returned false before triggering any events.
Maybe this wasn't your problem but hopefully it'll help someone else out there...
Validate your JSON response..
In my case I had an extra comma (,)..
Nearly in-valid response may cause this issues

Categories