I've got a service that gets details for a certain user profile:
.factory('UserDetails', function($resource, $rootScope) {
return $resource('../html/app/user_details.json', {}, {
query:
{
method: 'GET',
params: {},
isArray: true
}
});
})
I'm mocking the non-existent back-end with a plain JSON file for now.
I also need to be able to update the user profile - so I need to write a POST request (probably to the same end-point).
In my controller I currently use:
UserDetails.get({}, function(data) {
$scope.profile = angular.copy(data.user_details);
});
It would be elegant to be able to write
UserDetails.save({$scope.profile}, function(data){ /* .. */});
Is there a way to extend my UserDetails service to achieve this? How would I have to modify the service declaration?
You can use angular-mock for your purpouses.
See related Stackoverflow question
Related
I would like to know how to pass headers to AngularJS $resource method
Here is the factory method
.factory('DataRepository', function ($resource) {
return $resource(serviceUrlPrefix + '/api/v1/AppList/:id', { id: '#id' }, { 'query': { method: 'GET', isArray: false }, 'update': { method: 'PUT', AppList: '#req', headers: { 'X-Requested-With': 'XmlHttpRequest' } } });
});
Here is the call to the dataRepository
dataRepository.update({ id: req[uniqueIDColumn] }, req, function (data) {
},
function (error) {
});
This code works fine. But i have few queries
Question 1:
Rather than specifying the headers in the factory method , how can i specify it in the call to the factory method? I tried few methods but it didnt work out.
Question 2:
I specified the header in the update method in the factory. When i perform "Save" using that factory, that header has been taken by default. But i have specified it explicitly for PUT method. Right? Why and how?
Question 3:
If i would like to specify the header for the particular factory in common for all Http methods, what is the way to do it?
Question 4:
What is the nomenclature for passing the parameters and the significance of "#" symbol before parameter and also in the below part, AppList is the parameter name used in the WebAPI, is it mandatory that it should match the parameter name in the WebAPI method, if its not matching, its not working:(
AppList: '#req'
I'm afraid we don't use $resource, but it does depend on $http. We configure the header with the below. Not sure about the rest of your questions.
I will say that we also do not use $http directly. We created our own "requestHelper" service that we inject everywhere. That allows us to inject things before making calls to $http as well as catch the response before passing the result on to the real caller. Helps with common error handling.
Configure headers for $http:
module.config(['$httpProvider', function ($httpProvider) {
$httpProvider.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
}]);
I have a basic factory returning a $resource called User:
angular.module('appServices').factory('User', [
'$resource',
function($resource){
return $resource('http://localhost:3000/users/:username', {username:'#username'});
}]);
In my UserController.js file I try to do something fairly simple:
angular.module("myApp").controller('UserController', ['$scope', '$routeParams', 'User',
function($scope, $routeParams, User){
var user = new User({email:'user#example.com', username: 'johndoe', password: 'mySecurePassw0rd!'});
user.$save();
}]);
I expect the $save() function to make a POST request to /users, however in the console I get http://localhost:3000/users/johndoe 404 response, because the route is obviously not set up on the server for POST requests with the username parameter appended...
Why is it that it does so in my code sample ? From the examples I have seen on the internet, the $save() function does not take into account the username and should directly POST to /users in my case.
Any help would be appreciated!
EDIT
I think I got the error (as always, immediately after posting the question on SO...)
It is probably because of the default parameter {username: "#username"} I specified in the $resource, as it expects there to be a username parameter by default ?
So the correct way to do it would be to return this resource from the factory:
return $resource('http://localhost:3000/users/:username');
correct ?
Username is a natural primary key. Unless you use switch to a surrogate one (generated on server side), you will need two different methods for entity creating and updating:
var User = $resource(
'/users/:username',
{
username: '#username'
},
{
save: {
methods: 'POST',
url: '/users'
},
update: {
method: 'PUT'
}
}
);
I've got a problem with service configuration. I want to display one user by this function:
$scope.findOne = function() {
$scope.user = Users.get({
userId: $stateParams.userId
});
};
But I am in trouble with User service :( I don't know, how should I change my the code to avoid angular error:
Error in resource configuration for action object. Expected response
to contain an array but got an {2}
Here is a code of my actual working service (without function findOne working of course:))
'use strict';
angular.module('users').factory('Users', ['$resource',
function($resource) {
return $resource('users', {}, {
update: {
method: 'PUT'
},
remove: {
method: 'DELETE',
url: 'users/:id',
params: {id: '#_id'}
}
});
}
]);
At a guess, I'd say your users API endpoint is expecting /users/:userId for GET requests. Your code at the moment will request /users?userId=nnn. You need to add an action for get with the ID in the URL, eg
return $resource('users', {id: '#userId'}, {
get: {
method: 'GET',
url: 'users/:id',
isArray: false
},
// etc
You can also make users/:id the default URL as long as it doesn't interfere with your other action configurations.
I'm trying to grab a single result from my expressjs api from within my AngularJS factory.
The factory looks like this and grabs all posts from my api(written in expressjs and getting data from mongodb), which is working fine:
angular.module('bonsaiService', ['ngResource']).
factory('bonsaiService', function($q,$resource) {
var bonsaiResource = $resource('http://localhost:8888/api/bonsais/:bonsaiId',{},{
get:{
method: 'GET',
params:{bonsaiId:''},
isArray: true
}
});
return {
get:function(){
var q = $q.defer();
bonsaiResource.get({
},
function(resp){
q.resolve(resp);
},function(httpResponse){
q.reject(httpResponse);
});
return q.promise;
}
//find by id
};
});
What i've tried so far is adding :bonsaiId after the $resource url and adding params for that id like this: params:{bonsaiId: ''}.
The server part (expressJS) look like this:
router.route('/bonsais/:bonsaiId')
.get(function(req,res){
Bonsai.findOne(req.params.bonsaiId,function(err,bonsai){
if(err)
res.send(err);
res.json(bonsai)
})
})
When I call a local url (with and existing _id from mongodb) it works fine and returns my data in json :
http://localhost:8888/api/bonsais/536be2e2ae54668818000001
Now in the controller im trying to get this data in my scope, which is not working.
bonsaiService.get({bonsaiId:$routeParams.bonsaiId}).then(
function(data){
$scope.trees = data;
console.log(data);
});
How do I make this work?
You could use a more simple approach here.
The query method for $resource already defines a GET on an array, which is QUERY.
Why not write your service this way :
.factory('bonsaiService', ['$resource',
function($resource) {
return $resource('http://localhost:8888/api/bonsais/:bonsaiId', {
bonsaiId: '#bonsaiId'
});
}
])
And in your controller, it would work like this :
bonsaiService.query({
bonsaiId: $routeParams.bonsaiId
}, function success() {
//Your code
}, function err() {
//Your code
});
Don't forget to inject the service in the controller or the app file, if it's not done already.
I have a simple User $resource that uses the default $http cache implementation like so:
factory('User', function($resource){
return $resource(endpoint + '/user/current/:projectId', {},
{get:
{
cache: true,
method: 'GET'
}
}
);
})
This works very well, i.e. my server is only called once in my application, then the value is fetched from cache.
But I need to refresh the value from the server after a certain operation. Is there an easy way to do that?
Thanks.
Keep the boolean and get the $http cache:
var $httpDefaultCache = $cacheFactory.get('$http');
Then you can control it like any another cache made with $cacheFactory, a usage instance provided below:
$httpDefaultCache.remove(key);
// Where key is the relative URL of your resource (eg: /api/user/current/51a9020d91799f1e9b8db12f)
Instead of using a boolean argument in the cache property of each action you can pass on a cache instance created with $cacheFactory which you can have more control over (i.e. clear the cache).
Example usage:
app.factory('Todos', function($resource, $cacheFactory) {
var cache = $cacheFactory('todo');
return $resource(apiBaseUrl + '/todos/:id', { id: '#id' }, {
'get': { method: 'GET', cache: cache },
'query': { method: 'GET', cache: cache, isArray: true }
});
});
I came across this thread looking for something similar, but found that $resource will manage the cache for you automatically, so there's no need to force the cache to be cleared.
The idea is that if you have a resource that you can query, that query response will be cached, but if you save something for that same resource, the previously cached data must be invalid, so it is cleared for you. It makes sense that it would work this way.
Here's some code I use to do this (you can ignore the possibly odd-looking factory creation part and pay attention to the "class" body).
'use strict';
sampleApp.players.$ng.factory('sampleApp.players.PlayerService', [
'$log',
'$resource',
sampleApp.players.PlayerService = function ($log, $resource) {
var service = {};
$log.info('Creating player resource.');
var Player = $resource('/api/players', {}, {query: {
isArray: true,
cache: true,
method: 'GET'
}});
service.addPlayer = function(playerName) {
$log.info('Saving a new player.');
return new Player({name: playerName}).$save();
};
service.listPlayers = function () {
$log.info('Fetching players.');
return Player.query();
};
return service;
}]);
If you call the listPlayers function several times, the first call makes a http get request and all subsequent calls are cached. If you call addPlayer though, a http post is performed as expected, and then the next call to listPlayers will perform a http get (not cached).
This keeps you out of the business of managing someone else's ($http) cache and trying to keep up with which url's are being used for requests and which are clearing caches at the right times.
I suppose the moral of the story here is to work with the library and all will be well... except for any bugs or incomplete features, but Angular doesn't have any of those ;)
p.s. This is all running on AngularJS 1.2.0.