Grails/Angular $http() not binding Command Objects - angularjs

We are currently working on upgrading our project from Grails 2.2 to Grails 3.0 and have run into numerous issues. The one I'm currently working on has to do with Angular $http() and Command Objects not data binding correctly.
We currently let Grails bind all of our Command objects in our Controller methods. This is done EVERYWHERE and it would be a lot of work to go manually bind everything (Only solution I can come up with right now). We know we need to do this eventually (more control. Grails binds incorrectly constantly) but would rather not unless forced to.
class MyController {
def updateStatus(StatusCommand cmd) {
println("params: ${params}")
println("cmd: ${cmd}")
// example with objectId = 1 being passed in as http() data
// params will be: objectId: 1
// cmd will be: cmd.objectId: null
}
}
Our previous solution involved an interceptor which would take the request.JSON fields and dump them into params if there was no value already in params.
def beforeInterceptor = {
if (request.JSON) {
request.JSON?.each { key, value ->
if (!params[key]) {
params[key] = value
}
}
}
}
With Grails 3.0 Interceptors are now defined outside of the controller separately so I have created the following to replicate what we had before.
class MyInterceptor {
MyInterceptor {
matchAll()
}
boolean before() {
if (request.JSON) {
request.JSON?.each { key, value ->
if (!params[key]) {
params[key] = value
}
}
}
}
}
Unfortunately, this doesn't seem to work in Grails 3.0 like it did previously. While the values do seem to be mapped correctly on 'params' the Command Object has everything set to null. I can only assume that the Command Object is now being 'created' before the interceptor has run. Command Objects are correctly bound from our $.ajax() calls since the values are set into params and not request.JSON.
Is there a way to somehow get that created Command Object in the interceptor to set it manually or does anyone have any other ideas for a solution?
EDIT: Example of $http() angular call
var data = { objectId: 1 };
$http({
method: 'POST',
url: myUrl,
data: data
}).success(function() {
//stuff
});

Related

im trying to update database record with function return Ionic, Back&

Hi I'm trying to update my database with function that returns a number
$scope.sum = function()
{
return $scope.inp + $scope.points;
};
this function will update the record in object points, column name and id 1:
$scope.addPoint = function() {
PointService.addPoint($scope.sum, 1)
.then(function(result) {
$scope.inp = 0;
getMyPoints();
});
}
addPoint = function(id,points)
{
return $http.put(getUrlForId(1),points,name);
}
the error is: Error details: Cannot convert type 'int' to 'System.Collections.Generic.Dictionary'
the data type of the field is Float.
Any idea what is wrong with the code?
you are passing function reference to PointService.addPointer(),
use this:
$scope.addPoint = function() {
PointService.addPoint($scope.sum(), 1) // NOT PointService.addPoint($scope.sum, 1)
.then(function(result) {
$scope.inp = 0;
getMyPoints();
});
}
this will execute your function and pass the output (id parameter) to addPoint function, further, for more safe side, you can return Number from $scope.sum() i.e.
$scope.sum = function()
{
return Number($scope.inp + $scope.points);
};
This looks like an issue with how you're contacting Backand. You use the following code to send your points over:
addPoint = function(id,points)
{
return $http.put(getUrlForId(1),points,name);
}
This is an older version of calling the Backand API that is manually constructing a PUT request, and putting "points" and "name" as the "Data" and "config" parameters to $http. With an object update via PUT, you'll need to provide the updates as an object. So if you wanted to update the points and the name of the object (and I'm doing some assuming based upon what I can tell from the code snippet above), you'd need to encapsulate these properties in an object that has the following general format:
{
"field_name_1":"new value_1",
"field_name_2":"new value_2",
etc ...
}
This should then be sent as the body of the request. So, for your code, change it to the following and see if this helps:
addPoint = function(id,points)
{
return $http.put(getUrlForId(1),{points: points, name: name});
}
To give more info on why you're seeing this particular error, Backand is depending on this JSON format in the body. Our platform should definitely do more validation (and I'll create a ticket for the devs to handle non-conforming input more gracefully), but at the moment we simply take the body of the request, convert it to a dictionary object, then begin the requested operation. As your code above sends only "1.0" as the body, this fails the conversion into a dictionary, causing the stack exception and the issue you are seeing.
As a note, we offer a new SDK that encapsulates these HTTP methods, performing the authentication header generation and HTTP messaging for you, providing promises to handle responses. You can find it on our Github page at https://github.com/backand/vanilla-sdk. To make the same call using the new SDK, the code would resemble the following:
backand.object.update("your object name", 1, {name: name, points: points})
.then(function(response){
console.log(response.data);
});

Angular $resource returns outbound object when webservice returns null

I have a java webservice that I use for validation of unique fields.
It takes a string and searches the database for entities that already have that value in one of their fields. The java Spring endpoint
#RequestMapping(value = "/value", method = RequestMethod.POST)
#ResponseCaching(mode = Mode.NEVER)
#ResponseBody
public Keuze findByValue(#RequestBody final String value) {
return getService().findByvalue(value);
}
The angular $resource object is:
var resource = $resource('service/:entity/:field', {}, {
uniqueValue: {
method: 'POST',
url: 'service/:entity/:field',
cache: false
}
});
The function in the validation service that does the validation:
function uniqueValue(entity, field, value) {
return resource.uniqueValue({entity:entity,field:field}, angular.toJson(value)).$promise;
}
I call this function in a directive, like so:
validationService.uniqueValue('keuzes', 'omschrijving', viewValue).then(function(keuze) {
ctrl.$setValidity('unique', !keuze || keuze.id == scope.keuze.id);
});
The problem I'm running into is that angular resource seems to return the object that was sent to the webservice, when that webservice itself returns null. Only when the webservice returns an object it works correctly. Say the user enters the value "test", then it seems in the directive that the webservice has returned the following:
{
0: """,
1: "t",
2: "e",
3: "s",
4: "t",
5: """
}
Checking the Chrome developer tools clearly indicates that the webservice did its work correctly and returned nothing.
I previously implemented this with angular $http, and that worked fine. Is this behaviour of angular $resource intentional and am I missing something, or is this a bug? I couldn't find any other references to this behaviour elsewhere which makes me think I'm doing something wrong.
PS: I'm using angular 1.3.2 at the moment.
According to the docs it seems intentional that you get the resource in the success handler:
On success, the promise is resolved with the same resource instance or collection object, updated with data from server. This makes it easy to use in resolve section of $routeProvider.when() to defer view rendering until the resource(s) are loaded.

Is there an equivalent to KnockoutJs' isObservable in AngularJs

I'm running through some samples on Asp.net Web Api 2 and the project I will eventually be working on is going to utilise AngularJs. A couple of the data-binding scenarios I'm working on will have angular's $watch implemented and as I understand it, having the deep flag set to true on this function will notify of new and old values, but not at the property level - only at the array object level. The ultimate goal is to be able to isolate a property change and send this change as a PATCH request to Web Api rather than send the entire object as a PUT request.
The sample I have is currently using knockoutJs and the approach has a model that represents the data where the property values that need to be watched are set as observable(propName). My question is basically whether or not I can convert the following knockoutJs code to something similar in Angular:
self.watchModel = function (model, callback) {
for (var key in model) {
if (model.hasOwnProperty(key) && ko.isObservable(model[key])) {
self.subscribeToProperty(model, key, function (key, val) {
callback(model, key, val);
});
}
}
}
self.subscribeToProperty = function (model, key, callback) {
model[key].subscribe(function (val) {
callback(key, val);
});
}
with the model looking something similar to the following:
var obsEmployee = {
Id: employee.Id,
Name: ko.observable(employee.Name),
Email: ko.observable(employee.Email),
Salary: ko.observable(employee.Salary),
}
I'm sure that there's either an equivalent method available in Angular, or the difference in implementation is significant enough that there is another approach.
There is not. Angular uses dirty checking instead of a subscription/notification framework. Thus, every observed variable is observable. See here for more: http://www.sitepoint.com/understanding-angulars-apply-digest/

Understanding BackboneJS flow

I have been given a Project which is written entirely in Backbone.js, which I am supposed to change according to our specific needs. I have been studying Backbone.js for the past 2 weeks. I have changed the basic skeleton UI and a few of the features as needed. However I am trying to understand the flow of the code so that I can make further changes.
Specifically, I am trying to search some content on Youtube. I have a controller which uses a collection to specify the url and parse and return the response. The code is vast and I get lost where to look into after I get the response. I tried to look into views but it only has a div element set. Could someone help me to proceed. I wont be able to share the code here, but a general idea of where to look into might be useful.
Code Snippet
define([
'models/youtubeModelForSearch',
'coretv/config',
'libs/temp/pagedcollection',
'coretv/coretv'
],function( youtubeModelForSearch, Config, PagedCollection, CoreTV ) {
"use strict";
return PagedCollection.extend({
model: youtubeModelForSearch,
initialize: function() {
this.url = 'http://gdata.youtube.com/feeds/api/videos/?v=2&alt=json&max-results=20';
},
fetch: function(options) {
if (options === undefined) options = {};
if (options.data === undefined) options.data = {};
//options.data.cmdc = Config.getCMDCHost();
//CoreTV.applyAccessToken(options);
PagedCollection.prototype.fetch.call(this, options);
},
parse: function(response) {
var temp = response.feed
/*temp["total"] = 20;
temp["start"] = 0;
temp["count"] = 10; */
console.log(temp);
return temp.entry;
},
inputChangeFetch: function(query) {
this.resetAll();
if(query) {
this.options.data.q = query;
// this.options.data.region = Config.api.region;
//this.options.data.catalogueId = Config.api.catalogueId;
this.setPosition(0);
}
}
});
});
Let's assume your collection endpoint is correctly set and working. When you want to get the data from the server you can call .fetch() on you collection.
When you do this, it will trigger an request event. Your views or anybody else can listen to it to perform any action.
When the data arrives from the server, your parse function is called, it is set using set or reset, depending the options you passed along fetch(). This will trigger any event related to the set/reset (see the documentation). During set/reset, the data retrieved from your server will be parsed using parse (you can skip it, passing { parse: false }.
Right after that, if you passed any success callback to your fetch, it will be called with (collection, response, options) as parameters.
And, finally, it will trigger a sync event.
If your server does not respond, it will trigger an error event instead of all this.
Hope, I've helped.

AngularJS preprocess specific $http requests

I'm sending a model to server via $http.post, but, say, empty dates must be deleted, ids must be converted to int, in float values comma must be replaced with dot. These are restrictions of the server-side json api, so I'm looking for a way to modify $http request. Complicated part is that the modification rules depend on a api method name, which itself is a part of request.
The most straightforward way is to declare a modifying function and pass model to that function right before $http.post
$scope.method1Adapter = function(model) {
var data = angular.copy(model);
// 30 lines of modification code
return data;
};
$http.post("/api", {method: "method1", "data": $scope.method1Adapter($scope.data)})
but I think it's a little bit spaghettysh.
Better way is a factory that gets a method name and returns an adapter callback.
coreApp.factory("httpAdapter", function() {
return {
get: function (method) {
if (method == 'method1') {
return function (model) {
var data = angular.copy(model);
// modifications
return data;
}
} else {
return function (model) {
return model;
}
}
}
}
});
so i can add this to $httpProvider.defaults.transformRequest callbacks chain
coreApp.config(function($httpProvider, httpAdapterProvider) {
$httpProvider.defaults.transformRequest.unshift(function(post) {
if (post && post.data && post.data) {
post.data = httpAdapterProvider.$get().get(post.method)(post.method);
}
})
});
And still I don't like that, because api for my application has 16 methods, and it would require 5 adapters which is about 100 lines of code hard to maintain.
Any ideas about more clean and neat solution? Thank you.
I wouldn't chain adapters here because, as you said, it would be hard to maintain.
My advice would be to use the $http interceptors (not the responseInterceptors, which are deprecated, but the normal one, see http://docs.angularjs.org/api/ng.$http).
Notice in that you have access to the "config" object that has the request url, amongst other interesting properties.
It won't be superneat but at least the problem can be contained in one isolated part of your codebase.

Resources