I'm trying to add caching to a resource, but at each call, a new ajax request would be made. Here:
app.factory('States', function($resource,$locale,$cacheFactory) {
var cache = $cacheFactory('statesCache');
var StatesService = $resource( "api/ref/states-:country", {}, {
query: {
cache: cache, //With true it doesn't work either
method: 'GET',
isArray: true
}
});
return {
getList: function( country ) {
console.log( cache.info() );
return StatesService.query({
country: (country || "CA").toLowerCase()
});
}
}
});
then each call to States.getList( "CA" ); triggers a new Http request. The console says each time that the custome cache size is still 0: Object {id: "statesCache", size: 0}
Angular version is v1.2.0-rc.3 was 1.0.8
The problem was Chrome didn't invalidated the cache for the angular files. It was still loading angular 1.0.8.
I've cleaned the cache and decently updated angular-resource version, now it works.
Related
I have a very simple address application in AngularJS. It connects to an API using $resource.
The ID I use is the mobilephone number of the person (I know it's not the best way to do it, but it's just an sample application to show a 3-tier application setup) So I've 2 pages with the same form:
The problem i'm facing is that it uses the same $resource for saving a new address and saving an edited address. When iḿ saving a new address it has to use the url http://127.0.0.1:5000/api/contacts/ without an id appended on it (the new ID it will get on the api/database side is the filled in mobile phone number)
When I edit an existing address and click the save button it has to use another url; http://127.0.0.1:5000/api/contacts/#mobilePhone.
So i've read the angular documentation on https://docs.angularjs.org/api/ngResource/service/$resource which states that you can override your paramDefaults in your actions. So that is what I try to do using this code:
$resource('http://127.0.0.1:5000/api/contacts/:id',{id:'#mobilePhone'},{
get: {
method: 'GET'
}, update: {
method: 'PUT'
}, save: {
method: 'POST',
id:''
}
},{});
which seems to be correct given the information. It appends the mobilePhone number on every GET and every PUT the get and update method respectively. When it calls the save method it should override the :id with an empty string, but it doesn't do that.
Clearly i'm doing something wrong.
If you need more code snipets let me know, I tried to keep it as clean as possible.
Update:
This is how I call the save method:
....
.controller('MovieCreateController',function($scope,$state,$stateParams,Movie){
$scope.movie=new Movie();
$scope.addMovie=function(){
$scope.movie.$save(function(){
$state.go('movies');
});
}
}
And this is the edit method:
....
.controller('MovieEditController',function($scope,$state,$stateParams,Movie){
$scope.updateMovie=function(){
$scope.movie.$update(function(){
$state.go('movies');
});
};
$scope.loadMovie=function(){
$scope.movie=Movie.get({id:$stateParams.id});
};
$scope.loadMovie();
});
There is not much code in your question, so I'll try to explain what you should do to use $resource.
Have a look at following code:
// The $resource service is a helper to create a 'constructor' function
// Contact below is a function
var Contact = $resource('http://127.0.0.1:5000/api/contact/:id',{id:'#mobilePhone'}, {
get: {
method: 'GET' // You don't need to override the GET
}, update: {
method: 'PUT'
}, save: {
method: 'POST'
}});
// Be sure to create an 'entity' from the Contact $resource
// The 'new' will create a $resource instance with $update, $save methods overridden methods
var contact = new Contact({name: 'foobar'});
contact.$save(); // Will send a POST request
contact.mobilePhone = 2; // This is your id !
contact.$update(); // Will send a PUT request
If your resources always have a RESTful representation, I suggest that you use, as per suggested at angular discussion on issue#9807:
resource.prototype.$save = function() {
if (this.id) {
return this.$update();
} else {
return this.$create();
}
};
..rather that always overriding your $resource methods.
This is how you need to call your save method
$scope.movie=new Movie();
$scope.addMovie=function(){
$scope.movie.$save(function(){
$state.go('movies');
});
}
This is how you need to call your edit method
$scope.movie=new Movie();
$scope.updateMovie=function(){
$scope.movie.$update({ "mobilePhone": $stateParams.id},function(){
$state.go('movies');
});
};
For that you need to create a factory for $resource.which is always recommendable
Try this
.factory('movie', ['$resource', function ($resource) {
return $resource('http://127.0.0.1:5000/api/contacts/:id',{id:'#mobilePhone'},{}, {
update: { method: 'PUT' },
query: {
method: 'GET',
isArray: false
}
})
}])
Inject your factory in your controller
.controller('MovieCreateController',['$scope','$state','$stateParams','movie',function($scope,$state,$stateParams,Movie){
}]);
JSON/XML from REST
{
litm: "T00000245",
lotn: "00004"
}
<jdeSerials>
<litm>T00000245</litm>
<lotn>00004</lotn>
</jdeSerials>
AngularJS controller
//Searching a product with serial number/LOTN
$scope.searchProduct = function () {
var lotn = $scope.jdeSerials.lotn;
console.log("searchProduct---->" + lotn);//log-->searchProduct---->00004
$scope.JdeSerials = lotnService.get({id: lotn}, function() {
console.log($scope.jdeSerials);//log-->[object Object]
console.log($scope.jdeSerials.litm);//log-->undefined!!!!!
});
//var litm = $scope.jdeSerials.litm;
//$scope.jdeproduct = productService.get({id: litm});
};
AngularJS service
angular.module('lotnService', ['ngResource'])
.factory('lotnService', ['$resource',
function ($resource) {
console.log('------lotmService-----');
return $resource(
'http://localhost:8080/RMAServer/webresources/com.pako.entity.jdeserials/:id',
{},
{
update: { method: 'PUT', params: {id: '#lotn'} }
});
}]);
Question
How can I get a value to $scope.jdeSerials.litm? Is there any better idea to solve this like creating a service which handles this two GETs? I think that reason is the GET method is asynchronous, but what is the best solution to handle situations like this?
EDIT/update
I changed the service call like this:
$scope.JdeSerials = lotnService.get({id:lotn})
.$promise.then(function(jdeSerials) {
$scope.jdeSerials = jdeSerials;
console.log("1--------------->LITM:"+$scope.jdeSerials.litm);
});
I got the LITM, BUT I got the errormessage as well:
TypeError: Cannot read property 'then' of undefined
Try to create a get method in your resource.
angular.module('lotnService', ['ngResource'])
.factory('lotnService', ['$resource', function ($resource) {
return $resource( 'http://localhost:8080/RMAServer/webresources/com.pako.entity.jdeserials/:id',
{},
{
get: { method: 'GET', params: {id: '#lotn'}},
update: { method: 'PUT', params: {id: '#lotn'} }
});
}]);
Then in your controller, call method get from service:
lotnService.get({id:lotn}).$promise.then(
function(jdeSerials) {
$scope.jdeSerials = jdeSerials;
console.log("1--------------->LITM:"+$scope.jdeSerials.litm);
});
What angular js version are you using?
Does the following work ?
lotnService.get({id:lotn}).then(
function(jdeSerials) { ... }
);
(without the $promise)
I was browsing the docs and also angular-resource.js source for previous versions and it appears that the synthax has changed somewhere along the line.
On angular-resource.js 1.2.0 source:
The Resource instances and collection have these additional properties:
$promise: the {#link ng.$q promise} of the
original server interaction that created this * instance or
collection.
On 1.0.8 there is no mention of the $promise propery, however.
When POSTing to an endpoint in a service layer to update a user's profile, I need to strip certain values from the request payload (the profile with the desired modifications from the client) and re-attach them in the response payload (the updated profile from the server). I am currently performing behavior using Angular's request and response transformers, like this:
myService.updateProfile = function (profile) {
return $http({
method: 'POST',
withCredentials: true,
url: root + 'users/profile',
data: profile,
transformRequest : requestTransformer,
transformResponse : responseTransformer
});
};
// the map used during transformation below
var myMap = {
0: 'foo',
1: 'bar',
2: 'etc'
};
// prependTransform() and appendTransform() are similar to the example provided in Angular transformer docs here:
// https://docs.angularjs.org/api/ng/service/$http#overriding-the-default-transformations-per-request
var requestTransformer = httpTransformer.prependTransform($http.defaults.transformRequest, function(profileRequest) {
profileRequest.myKey = myMap.indexOf(profileRequest.myValue);
delete profileRequest.myValue;
return profileRequest;
});
var responseTransformer = httpTransformer.appendTransform($http.defaults.transformResponse, function(profileResponse) {
profileRequest.myValue = myMap[profileRequest.myKey];
delete profileRequest.myKey;
return profileResponse;
});
I prepend a transformer to the default request transformers and append a transformer to the default response transformers. My question is, is there a better way to do this? Perhaps using interceptors, as documented here, instead? If so, how?
I think your solution is fine but if you want an alternative, you can intercept specific requests like so. HTTP interceptors are mostly useful for handling global HTTP requests/responses (auth, error handling, etc.).
In any case, the "response" payload should be taken cared of from the API/server-side.
$provide.factory('userProfileInterceptor', function() {
return {
request: function(config) {
if (config.url.indexOf('/users/profile') >=0){
if (config.params.myValue) delete config.params.myValue;
}
return config;
},
response: function(response) {
if (response.config.url.indexOf('/users/profile') >=0){
delete response.data.myKey;
}
return response;
}
};
});
$httpProvider.interceptors.push('userProfileInterceptor');
I am trying to use ngResource to consume a firebase rest API. This API uses slightly different URLs for retrieving collections vs individual records. For example, I have a collection that I access with:
https://angular-resource-test.firebaseIO.com/systems.json
But if I want to access an individual system I use:
https://angular-resource-test.firebaseIO.com/systems/1.json
How do I specify a parameter-based URL for this in the resource declaration?
Normally I would do something like
app.factory('System', ['$resource', function($resource) {
var System = $resource(
'https://angular-resource-test.firebaseIO.com/systems/:id.json',
{id: "#id"},
{update: {method: "PUT"}}
);
return System;
}]);
But that fails for the collection because it lacks the trailing .json. Alternatively, when I specify a URL that works for the collection, the case of selecting individual rows fails. See this jsfiddle for an example:
http://jsfiddle.net/D5E6w/15/
There are 2 methods that send an HTTP GET in a resource.
get : Expect to receive an object
query : Expect to receive an array
Your api isn't very restful and because of this, you will need 2 resources to do what you want since they use different URI (see Carl's answer). I dont know if you can edit your REST service, but the good way to do it would be :
https://angular-resource-test.firebaseIO.com/systems/ for a query (expect an array)
https://angular-resource-test.firebaseIO.com/systems/:id for a get. (expect an object)
With this service, you could use your resource :
var System = $resource(
'https://angular-resource-test.firebaseIO.com/systems/:id',
{id: "#id"},
{update: {method: "PUT"}}
);
You would do your calls like this :
var systems = System.query({});
var system = System.get({id:1});
Your problem should be solved by using isArray: true for your query and get methods for System and SystemType. It has to do with how your json is formatted on the server-side. See angular docs for expanded discussion. http://jsfiddle.net/D5E6w/24/
{
update: { method: "PUT" },
query: { method: "GET", isArray: true },
get: { method: "GET", isArray: true }
}
Here is a working example with both a collection of systems and a single record. Note the isArray value for each. http://jsfiddle.net/D5E6w/51/
here is my solution with replacing the entire $resource URL. I needed that because I am using HAL Rest response and when paginating for instance, I wanted to replace the entire URL and not only params in it.
app.factory('$rest', ['$resource', 'HALParser', function($resource, HALParser) {
return function($url) {
$url = ($url == null) ? 'http://localhost:8000/:type' : $url;
return $resource($url, {type: ''}, {
update: { method:'PUT' },
get : {
method: 'GET',
transformResponse: [function(data) {
return (new HALParser()).parse(angular.fromJson(data));
}]
}
});
}
}]);
and then,
app.controller('VendorsController', ['$scope', '$rest',
function($scope, $rest) {
$scope.data = $rest().get({type: 'vendors'});
$scope.create = function() {
$rest().save({type: 'vendors'}, {name: $scope.item});
};
$scope.load = function($item) {
console.log($item);
};
$scope.page = function($url) {
$scope.data = $rest($url).get();
};
}]);
I simply wrapped $resource return from my service info a function with an argument URL.
New to AngularJS and trying to get a grasp of the framework, and trying to build a basic CRUD app. I can't seem to figure out what is needed to Update an existing record. Here is my service:
angular.module('appServices', ['ngResource']).
factory('App', function ($resource) {
var Item = $resource('App/:AppId', {
//Default parameters
AppId: '#id'
}, {
//Actions
query: {
method: 'GET',
isArray: true
},
getById: {
method: 'PUT'
},
update: {
method: 'POST'
}
});
return Item;
});
I can run a basic Get all query, and getById to populate an edit form, but that's where I'm stuck. Here is example code for getById
$scope.apps = App.query();
$scope.getEdit = function(AppId) {
App.getById({id:AppId}, function(app) {
$scope.original = app;
$scope.app = new App(app);
});
};
$scope.save = function() {
//What type of information should go here?
//Do I need to make changes to the appServices?
};
I guess, I'm just not sure what's next concerning Updating existing information, or how the "app" object gets passed to the API, can anyone point me in the right direction, or show me a quick update method?
This is a really messy way of handling save operations in angular. For one - you should not be using PUT operations for retrieval requests and secondly - all of this is already built-in to angular. See below.
var Item = $resource( 'App/Details/:AppId', { AppId: '#id' } );
var item = Item.get({ id: 1 }, function( data ) {
data.setAnothervalue = 'fake value';
data.$save();
);
What I'm doing here is retrieving an "Item" and then immediately saving it with new data once it's returned.
Angular JS provides a stack of defaults already, including query, save, remove/delete, get.etc. And for most RESTful APIs, you really shouldn't need to add much, if anything at all. See the resource docs for more information, particularly the information on defaults: http://docs.angularjs.org/api/ngResource.$resource
Additionally, once you get a handle on that - you may want to use $save for both create/update operations, but using POST/PUT (RESTful conventions). If you do, see my article that I wrote about not too long ago: http://kirkbushell.me/angular-js-using-ng-resource-in-a-more-restful-manner/
After doing a bit more research, and reviewing Daniel's link (thanks). I got it working.
Controller method:
$scope.save = function() {
$scope.app.update();
};
Service Factory:
var Item = $resource('App/Details/:AppId', {
//Default parameters
AppId: '#id'
}, {
//Actions
query: {
method: 'GET',
isArray: true
},
getById: {
method: 'PUT'
},
update: {
method: 'POST'
}
});
Item.prototype.update = function (cb) {
console.log(this.AppId);
return Item.update({ AppId: this.AppId },
angular.extend({}, this, { AppId: undefined }), cb);
};
return Item;