Populate a list of ngResource from a local variable? - angularjs

I have a basic ngResource defined per:
angular.module('factories', []).factory('SeedSource', [
'$resource', function($resource) {
return $resource('/seed-sources/:id/', {
id: '#id'
}, {
update: {
method: 'PUT'
}
});
}
]);
And in my Controller I would like to be able to populate a list of this resource from a local variable instead of the traditional .query() method, for instance:
$scope.seed_sources = json_array_of_seed_sources;
This works up until the point I need to call an ngResource method such as:
seed_source.$save()
I know I can go the long way and just add each item to $scope.seed_sources individually each as a new Seeder(...) but I was hoping there might be a cleaner way of achieving this?

If you want to save an array of resources, use the class method:
for (let i=0; i<seed_sources.length; i++) {
SeedSource.save(seed_sources[i], function(source) {
angular.copy(source, seed_sources[i]);
});
};

Related

$resource - proper configuration for ngResource

I have 3 objects in my application, Games, Questions, and Answers.
The classes are configured as such:
class Game{
id;
Question[] questions;
}
class Question{
id;
text;
Answer[] answers;
}
class Answer{
id;
text;
}
I am trying to correctly configure an ngResource to handle this class setup. Ideally, what I'd like to achieve is something like this:
app.factory('gameRepository', function($resource){
var gameResource = $resource('/api/Games/:id', { id: '#id' });
return {
get: function(id){ return gameResource.get({id: id}); }
};
});
app.controller('myController', function(gameRepository){
var game = gameRepository.get(17);
var questions = game.$getQuestions();
var answers = questions[0].$getAnswers();
});
I know that some of this can be achieved by doing this:
var gameResource = $resource('/api/Games/:id/:action', { id: '#id', action: '#action' },
{
getQuestions: { method: 'GET', isArray: true, params: { action: 'Questions'}
},
);
However, I get stuck after this point. Ideally, what I'd like to do is have the $getAnswers method return an object from a different resource (a new questionsResource) and then in turn, have a $getAnswers method that returns from an answers resource. The goal here is to keep the methods on the actual resource object instead of extracting them to a separate factory/service call.
Additionally, I'd like to be able to request a specific question from the repository. Something like this:
var game = gameRepository.get(17);
var question = game.$getQuestion(1);
As far as I can tell, there's no way to pass a specific parameter to a $resource custom action the way I'm using them.
Can anybody point me in the right direction? Thanks!
This ended up being way easier than I thought.
The $resource function creates a function that all objects returned from the resource inherit. As a result you can do something like this:
gameResource.prototype.$getQuestion = function(id){ ... }

AngularJS REST service calls

I'm looking to add factory/service calls for the URLs below to an AngularJS project in a way that follows the DRY principle. The project uses ngResource.
http://localhost/vehicles/{type:car|truck}/drive/{2wd|4wd}?sort={"start":"1", "limit':"10", "sortBy": "make"}
http://localhost/vehicles/{type:car|truck}/drive/{2wd|4wd}/count
http://localhost/vehicles/bestselling/{type:car|truck}?sort={"start":"1", "limit':"10", "sortBy": "make"}
http://localhost/vehicles/bestselling/{type:car|truck}/count
All calls are HTTP GET
The URL path parameters "{type:car|truck}" can be either one of "car" or "truck" the same goes for "{2wd|4wd}".
The URLs ending with count return the number of items (for pagination); the rest return the list of items to be displayed.
How can I define the factory/service calls for these resources in Angular? I have not had any luck finding an answer for this; the closest I've found is this
Disclaimer: I have no experience with AngularJS
My solution:
services.js
resources.factory('Constants', [
function() {
return {
RESOURCE_URL: "http://localhost/vehicles"
}
}
]);
resources.factory('Rest', ['Constants', '$resource', function(C, $resource) {
return {
BestSellingCount: $resource(C.RESOURCE_URL + '/bestselling/:type/count', {
type: '#type'},{})
BestSelling: $resource(C.RESOURCE_URL + '/bestselling/:type', {
type: '#type'}, {
getBestSelling: { method:'GET', params: {sort: {"start":"1", "limit':"10", "sortBy": "make"}}, isArray: true)}}})
}]);
And in my controller
$scope.bestsellingtrucks = Rest.BestSelling.getBestSelling({type:"truck"});
The default sort and pagination values get passed in as a query string

AngularJS: $resource with nested resources

I am using AngularJS $resource model to REST API. I have got something like this:
angular.module('libraryapp')
.factory('Book', function($resource){
return $resource('books/:id');
});
I am using in these way:
Book.get({ id: 42 }, function(book) {
console.log(book);
});
But I also want an endpoint to a subresource, let's say:
GET /books/:id/comments
How should I define it in module? May I extend Book in some way, to use it like this
Book.get({ id: 42 }).Comment.query(function(comments) {
console.log(comments);
});
You can easily reach nested RESTful resources with AngularJS $resource definitions.
The clue is to understand how the params parameter of each action definition (in the list of actions) in the $resource definition works. As the documentation says, it's an
Optional set of pre-bound parameters for this action. […]
angular.module('libraryApp').factory('Book', [
'$resource', function($resource) {
return $resource('books/:id/:subResource', {}, {
comments: { // The `comments` action definition:
params: {subResource: 'comments'},
method: 'GET'
}
});
}
]);
Given the above definition, you should still be able to use Book as before. For example Book.get({ id: 42 }) translates to a GET books/42/ request.
However, given the new :subResource part of the $resource URL ('books/:id/:subResource'), you now can generate a
GET books/42/comments
request by calling either Book.get({ id: 42, subResource: 'comments' }) or the much more short and elegant interface Book.comments({ id: 42 }) defined as your comments action.
As far as I know, you can't nest resources, but it's pretty simple to do what you're looking for:
You can define optional parameters which you can override in each resource (like category here) or even override the url (look at the otherUrl resource)
angular.module('libraryApp').factory('Book', [
'$resource', function($resource) {
return $resource('books/:id/:category', {}, {
comments: {
method: 'GET',
action: 'category'
},
otherUrls: {
method: 'GET',
url: 'books/:id/admin/:option'
}
});
}
]);
You may want to use Restangular instead as it handles nested resources and a clean and easy way.
As djxak pointed out, adding actions to the resource means that the returned value is the containing resource type, not the sub-resource type.
I solved a similar problem by creating a new resource with the sub-resource URL and modifying the prototype of the containing resource to add a function:
angular.module('libraryapp')
.factory('Book', function($resource){
var bookUrl = 'books/:id',
Book = $resource(bookUrl),
BookComment = $resource(bookUrl + /comments");
Book.prototype.getComments = function () {
return BookComment.query({id: this.id});
};
return $resource('books/:id');
});
The usage then becomes:
Book.get({ id: 42 }).getComments(function(comments) {
console.log(comments);
});
The only downside I see with this approach is that if you have a separate "Comment" resource that is accessed via a different URL, you have to duplicate the $resource initialisation code for the alternative endpoint. This seems a minor inconvenience though.

Angular JS - Manipulating Data in a Service / Factory and Making Sanitized Object Available to Controller

I currently have a simple factory like the following:
myFactory.factory('myFactory', function($resource){
return $resource('http://to/my/api', {}, {
query: {
method: 'GET'
}
});
});
I would like to move some of the data manipulation / sanitation that I am currently doing in my controller to my factory. I changed my factory to the following:
myFactory.factory('myFactory', function($resource){
var connection = $resource('http://to/my/api', {}, {
query: {
method: 'GET'
}
});
var filters = connection.query();
for(var i = 0; i < filters.data.length; i++){
// Get the name value in the data array
var name = filters.data[i].Name;
// Create new properties in $scope.filters and assign arrays to them. Each array is a separate filter
filters[name] = filters.data[i].Values;
}
return filters;
});
Essentially what I am doing in the above is reordering the structure of the object array returned from the AJAX call.
When I try to console log my 'filters' variable from the factory in my controller, I get an 'undefined' message. What is wrong with my above factory syntax?
Because you're not publicly exposing anything to return filters, try:
myFactory.factory('myFactory', function($resource){
return {
getFilters:function(){
var connection = $resource('http://to/my/api', {}, {
query: {
method: 'GET'
}
});
var filters = connection.query();
for(var i = 0; i < filters.data.length; i++){
// Get the name value in the data array
var name = filters.data[i].Name;
// Create new properties in $scope.filters and assign arrays to them. Each array is a separate filter
filters[name] = filters.data[i].Values;
}
return filters;
}
}
});
and call it like this in your controller:
$scope.filters = myFactory.getFilters();
Excuse the horrible formatting, here's a plunk.

Angular resource (ngResource) won't make the AJAX call when called inside a function

I'm trying to make an AJAX call with ngResource, In the code below 'a' and 'b' both print, but the AJAX call from Table.import() does not get made. If I move the AJAX call outside of onFileRead, then it works. What could be the problem?
var TableImportController = ['$scope','Table', 'project', 'table',
function($scope, Table, project, table) {
$scope.table = table;
$scope.project = project;
$scope.onFileRead = function(file) {
console.log('a');
Table.import({ data : file.data}, function() {
}, function() {
});
console.log('b');
};
}];
Where Table is an ngResource
.factory('Table', function($resource) {
var Table = $resource('/api/tables/:id:listAction/:itemAction',
{
id: '#id',
listAction: '#listAction',
itemAction: '#itemAction'
},
{
update: { method: 'PUT' },
import : { method: 'POST', params: { listAction: 'import' }},
}
);
return Table;
});
You are declaring $scope.onFileRead as a function.
What is calling onFileRead?
When you move the call outside of the function, it is being run as part of initialization.
What provides the input file?
Probably bind to the onFileRead function from something in your DOM.
I figured it out. It looks like I ran into this bug: https://github.com/angular/angular.js/issues/2794#issuecomment-18807158.
I solved the issue by wrapping the AJAX call (and eventually moved it to where the onFileRead callback is triggered) in a scope.$apply(function() { });

Resources