The restful api im calling wraps the desired payload (of models) within 'Payload'
I can ONLY get it to work this way, by 1. adding each into this.add(model) and 2. returning the array
Note: 'this' is a Backbone Collection
parse: function(resp, xhr) {
var that = this;
var ourPayload = resp.Payload;
ourPayload.forEach( function(model) { that.add(model);} );
return ourPayload;
},
In all examples I'd expect to simply do
parse: function(resp, xhr) {
return resp.Payload;
}
The format of the rest api is "{"Success":"true", ...., "Payload":[{model},{model},{model}], .... }
Can one explain the need to the collection.add(model)
The problem is that during a Collection fetch each Model's parse will also be called if they are defined. And likely the model's parse function will be defined in just this case: the RESTful API format is using a wrapper around the payload.
Backbone docs
After fetching a model or a collection, all defined parse functions
will now be run. So fetching a collection and getting back new models
could cause both the collection to parse the list, and then each model
to be parsed in turn, if you have both functions defined.
Therefore you need to define a parse as below. Note that 'Payload' is the name of your wrapper, be it payload, data, results,,
// Within a collection, it will not.
parse: function (response, xhr) {
if (_.isObject(response.Payload)) {
return response.Payload;
} else {
return response;
}
}
Now that clue came from nikoshr in this SO answer
Further in some cases, when your entrepid backend guy has wrapped a Model in an array then you gotta do the following with the Payload index [0]
parse: function (response, xhr) {
if (_.isObject(response.Payload)) {
return response.Payload[0];
} else {
return response;
}
}
This should work (it should be put into the collection):
parse: function (response) {
return response.Payload;
}
See the documentation. The Twitter Search API the doc refers to is documented here. The response you get has basically the same structure: you get an object, with a field containing the array of models. The same technique works well when one calls an ASMX webservice, see the example here.
Related
The API I am using requires a non-standard where clause if I try to search for a particular non-id field. The endpoint I need is:
http://127.0.0.1:4001/api/testusers/findOne?userName=Anton
So this will find me the first record in the testusers table whose column (userName) = 'Anton'.
My standard service is:
angular.
module('shared.testUser').
factory('TestUser', ['$resource',
function($resource) {
return $resource('http://127.0.0.1:4001/api/testusers/:id', {id:'#id'},//parameters
{
update: {
method: 'PUT' // To send the HTTP Put request when calling this custom update method.
}
});
}
]);
and my calling function is:
self.checkUsersEntryDirection = function(){ //NOT WORKING
self.testuser = TestUser.get({ username: 'anton' }, function() {
console.log(angular.toJson(self.testuser));
}); // get() returns a single entry
}
Clearly this doesn't work and I can't use the standard get approach. Can anyone think how this can be achieved?
You could create a secondary factory TestUserByName, and make the following changes:
angular.
module('shared.testUser').
factory('TestUserByName', ['$resource',
function($resource) {
return $resource('http://127.0.0.1:4001/api/testusers/findOne?userName:username', null,
{
update: {
method: 'PUT' // To send the HTTP Put request when calling this custom update method.
}
});
}
]);
Call the get action method with two parameters:
var params = {id: "findOne", username: "anton"};
self.checkUsersEntryDirection = function(){
self.testuser = TestUser.get(params, function() {
console.log(angular.toJson(self.testuser));
}); // get() returns a single entry
}
The id parameter will override the default and username parameter will be added as a query string.
From the DOCS:
Each key value in the parameter object is first bound to url template if present and then any excess keys are appended to the url search query after the ?.
Given a template /path/:verb and parameter {verb:'greet', salutation:'Hello'} results in URL /path/greet?salutation=Hello.
--AngularJS ngResource $resource Service API Reference
I have a simple service given here and I want to get a record(s) using a where clause, i.e. the endpoint would http://127.0.0.1:4001/api/testusers?userName=anton
The service is:
angular.
module('shared.testUser').
factory('TestUser', ['$resource',
function($resource) {
return $resource('http://127.0.0.1:4001/api/testusers/:id', {id:'#id'},//parameters
{
update: {
method: 'PUT' // To send the HTTP Put request when calling this custom update method.
}
});
}
]);
And the controller code I have tried is:
self.checkUsersEntryDirection = function(){ //NOT WORKING
self.testuser = TestUser.get({ username: 'anton' }, function() {
console.log(angular.toJson(self.testuser));
}); // get() returns a single entry
}
The error I get is
Error in resource configuration for action get. Expected response to contain an object but got an array (Request: GET http://127.0.0.1:4001/api/testusers)
Notice how it didn't add on the where clause. I thought that any extra properties provided would be assigned as where clause items??
thanks
I'm using ngResource to handle my models in my Ionic/Angular app and I'm having trouble figuring out how/if I can make custom actions on the resource.
I'm storing my model instances in local storage and when I update a record, I want to update the local storage as well. I have this working, but I'm having to copy and paste code for multiple instances and would like to keep it DRY.
LogEntry.update($scope.timeLog, function(data) {
// update local storage
for ( var i = 0; i < logEntries.length; i++) {
if(logEntries[i].id == $scope.timeLog.id){
logEntries[i] = $scope.timeLog;
}
};
localStorageService.set('LogEntries', logEntries);
});
Here is a situation where I update a record, and after the promise returns I update local storage. I would like to make this repeatable, how I envision it being possible (based on other things I've seen in other frameworks and other languages) is something like:
LogEntry.update($scope.timeLog, function(data) {
// update local storage
LogEntry.updateLocalStorage($scope.timeLog);
});
My resource looks like:
.factory('LogEntry', function(config, $resource) {
return $resource(config.apiUrl + 'logentries/:id/', {}, {
'update': {
method:'PUT',
params: { id: '#id' }
}
});
})
Maybe I'm missing something in the docs, but it's pretty short and I'm not seeing a way to do this. Is something like LogEntry.updateLocalStorage($scope.timeLog); possible to store with ngResource, or do custom actions like that need to come from somewhere else? I'd like to keep my model-related actions together if possible.
You could use the transformResponse method in your resource definition. It's kind of a hack since you don't actually need to alter the response, but it allows you to preform actions with the returned data:
{function(data, headersGetter)|Array.<function(data, headersGetter)>}
transform function or an array of such functions. The transform function takes the http response body and headers and returns its transformed (typically deserialized) version. By default, transformResponse will contain one function that checks if the response looks like a JSON string and deserializes it using angular.fromJson. To prevent this behavior, set transformResponse to an empty array: transformResponse: []
https://docs.angularjs.org/api/ngResource/service/$resource
.factory('LogEntry', function (config, $resource) {
return $resource(config.apiUrl + 'logentries/:id/', {}, {
'update': {
'method': 'PUT',
'params': {
'id': '#id'
},
'transformResponse': function (data, header) {
// do stuff with data
return data;
}
}
});
})
That way it will always execute when you get a response but you could also use the transformRequest method, which will always fire on request regardless if you're getting a response.
{function(data, headersGetter)|Array.<function(data, headersGetter)>}
transform function or an array of such functions. The transform function takes the http request body and headers and returns its transformed (typically serialized) version. By default, transformRequest will contain one function that checks if the request data is an object and serializes to using angular.toJson. To prevent this behavior, set transformRequest to an empty array: transformRequest: []
https://docs.angularjs.org/api/ngResource/service/$resource
Which you choose depends on your usecase. Using transformRequest you could always save to localstorage, even when remote is down.
I am trying to use Breeze to get data from a server into an AngularJS app, and although the server is sending the JSON data, the client app is not getting it. The closest I've gotten to identifying the issue using the debugger is to see that the following function getRemoteEntities(), which is part of a Factory, should return a promise but instead returns an empty Object {} when called with a valid entityURL and jsonAdapter:
[...]
var manager = entityManagerFactory.newManager();
[...]
return {
getRemoteEntities: function (entityUrl, jsonAdapter) {
var query = breeze.EntityQuery
.from(entityUrl)
.using(jsonAdapter);
return manager.executeQuery(query)
.then(function (results) {
return results;
})
.catch(function (error) {
return $q.reject(error);
});
}
}
I have checked, and the code does use the Breeze Angular Service as described here. I do not understand what is not working.
EDIT: Removing the .using(jsonAdapter) means that I am able to get and resolve the promise, suggesting that it might be doing something that messes it up. Here is an example (they all follow this pattern):
.value('jsonProfileResultsAdapter', new breeze.JsonResultsAdapter({
name: "xyz", // mild obfuscation
extractResults: function (data) {
var results = data.results;
if (!results) throw new Error("Unable to resolve 'results' property");
return results;
},
visitNode: function (node, parseContext, nodeContext) {
if (node) {
if (node.person && node.assignments) {
return {entityType: "EmployeeModel"}
}
}
}
}))
What is the jsonAdapter doing? That's an unusual feature (not wrong, just unusual). Maybe you're doing something inside it that blows up the promise.
Take it away and see what you get. If you get a promise, even a failed promise, then you're on to something.
I'm doing an application with Backbone.js and Require.js. I have an "Opportunities" Backbone collection and I needed to modify the fetch method because the data from the server comes inside a "results" object.
I did the trick by doing something I found in this question, at this point all looks good.
The problem is that I noticed that the fetch method is asking the server for the data TWO TIMES and not ONE as expected.
I am testing and now I found that if I remove this code: return Backbone.Collection.prototype.fetch.call(this, options); Backbone asks the url for the data only one time, obviously this code is causing the problem but I don't know the reason.
This is my Backbone collection
define([
'backbone',
'models/Opportunity'
], function(Backbone, Opportunity){
var Opportunities = Backbone.Collection.extend({
url: "/api/v1/opps/",
model: Opportunity,
// Need to have custom fetch because json data is coming inside a
// "results" array inside the JSON.
fetch : function(options) {
// store reference for this collection
var collection = this;
$.ajax({
type : 'GET',
url : this.url,
dataType : 'json',
success : function(data) {
// set collection main data
collection.reset(data.results);
}
});
// For some reason this is causing a double request
// to the server
return Backbone.Collection.prototype.fetch.call(this, options);
}
});
return Opportunities;
});
Someone knows the reason because this error is happening?
It's fetching it twice because you're using jQuery to fetch it directly, the calling the models own fetch method which will call an AJAX request as well.
If you want to return data.results back to your collection (and subsequently models), you can use the parse method, like:
var Opportunities = Backbone.Collection.extend({
url: "/api/v1/opps/",
model: Opportunity,
parse: function(data){
return data.results;
}
});