angularjs calling $resource parameters and dollar sign - angularjs

In the "wire up a backend" demo code from the angularjs site they set up a db call. From what I can tell they're extending the update function in order to add some extra parameters needed by the mongolab api.
angular.module('mongolab', ['ngResource']).
factory('Project', function($resource) {
var Project = $resource('https://api.mongolab.com/api/1/databases' +
'/angularjs/collections/projects/:id',
{ apiKey: '4f847ad3e4b08a2eed5f3b54' }, {
update: { method: 'PUT' }
}
);
Project.prototype.update = function(cb) {
return Project.update({id: this._id.$oid},
angular.extend({}, this, {_id:undefined}), cb);
};
Then they call the update property like this:
$scope.save = function() {
$scope.project.update(function() {
$location.path('/');
});
I've tried using this code to build a demo app using a local development server so I've omitted extending the update property as I don't need the extra $oid parameter. What I do need is to specify that the update method should use PUT. My code is like this:
var Unit = $resource('http:/localhost/api/unit/:id', {id:'#Unit_Id'},
{'update': { method: 'PUT' }});
And then calling it like this:
$scope.save = function () {
$scope.unit.update(function () {
$location.path('/unitlist');
});
But What I've discovered is that the code only runs with a dollar sign in front of update like this:
$scope.save = function () {
$scope.unit.$update(function () {
$location.path('/unitlist');
});
So here are my questions:
In the demo code, where is "update" actually added to the Project variable? As a parameter in $resource or using prototype to extend Project?
Why is update undefined in my code unless I prefix $ when I call it?

"In the demo code, where is "update" actually added to the Project
variable? As a parameter in $resource or using prototype to extend
Project?"
Adding the following code to end of the factory() method:
console.log('Project.update=' + Project.update)
console.log('Project.prototype.update=' + Project.prototype.update)
return Project;
we see that Project.update is a function, and that Project.prototype.update is another/different function. So there are actually two update functions. Function Project.update is a (non-GET) "class" action, so it is called without a $ prefix. update() is a function defined on the object returned by $resource -- i.e., Project. Indeed, we see that function Project.prototype.update calls Project.update(...).
In the EditCtrl, when data is returned from the server, the project variable is a resource "instance" object (which is different from the resource "class" object), hence it has (non-GET) "instance" actions, which are prefixed with $. One of those actions is $update. Add the following log() to the EditCtrl function to prove that project.$update() exists:
Project.get({id: $routeParams.projectId}, function(project) {
console.log('project.$update=' + project.$update)
So now we have a third "update" function. However, in the demo code, they don't use this project.$update() function -- they use the function they defined on Project.prototype instead.
"Why is update undefined in my code unless I prefix $ when I call it?"
In your code, Unit.update should be defined (this is a resource "class" action), and when your data is returned from the server, then $scope.unit.$update becomes defined (this is a resource "instance" action). Unit is a class, $scope.unit is an instance.
In your code, you probably don't need to use Unit.update because I'm guessing you first Unit.get() your data, and then you assign the returned data to $scope.unit, and then you update it using the instance (not the class), hence you are updating with $scope.unit.$update() and not Unit.update(), which is the recommended approach.

Ok, here it is from the docs
The action methods on the class object or instance object can be
invoked with the following parameters: HTTP GET "class" actions:
Resource.action([parameters], [success], [error]) non-GET "class"
actions: Resource.action([parameters], postData, [success], [error])
non-GET instance actions: instance.$action([parameters], [success],
[error])
So, when you when you extend the class (prototype), call it without the $ like normal, but when you add an action as a parameter of $resource prefix $.

As far as I get it they extend the prototype with this piece of code:
Project.prototype.update = function(cb) {
return Project.update({id: this._id.$oid}, angular.extend({}, this, {_id:undefined}), cb);
};
Then they create an instance of the Project in line 30 of the project.js
$scope.project = new Project(self.original);
And then they can perform an update by simply calling:
$scope.project.update(...)
If you got rid of extending the prototype of your service, you can't call $scope.unit.update because there exist no property of unit that is called update.
I hope it helps :)

Related

AngularJS Unit Testing: Attaching Data from $q.resolve() to object

I'm testing a service that uses another service for API calls, let's call this the data service. The data service is tested elsewhere, so I've abstracted it away with a simple implementation that contains empty functions; I'm returning data via a deferred object and Jasmine's spyOn syntax.
The trouble I'm finding with this approach is when the data is returned, it's not immediately available on the calling object, as it would be if I used $httpBackend. Aware I could just use $httpBackend, but I'd like to know if I've missed something (simple or otherwise) in this approach.
Example section of code I'm trying to test:
storeTheData = dataService.getSomeData();
storeTheData.$promise.then(function(data) {
/*this would work*/
console.log(data);
/*but this would not, when testing using $q*/
_.forEach(storeTheData, function(storedData) {
/*do something with each object returned*/
});
});
As a side note, I don't think the situation is helped by the ...$promise.then on another line, but ideally I wouldn't change the code (I'm providing test coverage to something written a while ago...)
Example of the test:
beforeEach(
...
dataService = {
getSomeData: function () { }
};
getSomeDataDeferred = $q.defer();
spyOn(dataService, "getSomeData").and.returnValue({$promise: getSomeDataDeferred.promise});
...
);
it(...
getSomeDataDeferred.resolve([{obj: "obj1"}, {obj: "obj2"}]);
$scope.$apply();
...
);
With the test described above, the console.log(data) would be testable as the data is accessible from being passed into the .then(). But the data is not immediately available from storeTheData, so storeTheData[0].obj would be undefined. On debug, I can see the data if I go through the promise that was attached to storeTheData via storeTheData.$$state.value
Like I said, I know I could use $httpBackend instead, but is there any way to do this with $q without changing the code under test?
I've not found a way to do this with $q.resolve, but I do have a solution that doesn't involve using the data service or changing the code under test. This is as good, because the main things I wanted to avoid were testing the data service as a side effect and changing the code.
My solution was to create a $resource object via $injector...
$resource = $inject.get("$resource");
...then return that in my basic implementation of the data service. This means I could use $httpBackend to respond to the request to an end point that isn't reliant on the data service's definition staying consistent.
dataService = {
getSomeData: function () {
/* new code starts here */
var resource = $resource(null, null, {
get: {
method: "GET",
isArray: true,
url: "/getSomeData"
}
});
return resource.get();
/* new code ends here */
}
};
...
$httpBackend.when("GET", "/getSomeData").respond(...;

Is it OK to fake a promise on a new angular resource?

I'm working with $resource in Angular 1.3.
I have a bunch of controllers with methods that work with the resource objects.
When the state of retrieval matters, these methods use the $promise property of the resource to ensure that they only process the objects after they are retrieved. These all work fine with existing and updated resource objects.
var ProposalResource = $resource(proposalUrl, {id: '#id'}, {'update': {method: 'PUT'}});
The resource objects are obtained by ProposalResource.get({id:....
However, when I create a new resource object in order to make a new object using new ProposalResource(..., the methods fail because the $promise property is undefined.
I've worked around this by setting the $promise property on the new resource object to a resolved promise.
This seems to work OK but it feels like a nasty kludge. The option of explicitly checking for whether or not the $promise property is defined in all the other methods is even less appealing though.
Am I doing the right thing?
I don't know why you need to use ProposalResource, but I usually use the $q provider.
That way you can do a simple function that returns a promise and you can call it from your controller methods.
Example service that uses a promise:
function someServiceMethod(params) {
//do something here, maybe create an object,maybe make a call with $http or something
var obj = createSomeObject(params);
//this resolves the object once the createSomeObject method or function have completed
$q.when(obj);
}
This approach is easier than doing the whole: var deferred = $q.defer(); and return deferred.promise after the deferred.resolves.
If you're using $resource, then I recommend just using $http provider from angular.
Here's $http
Here's $q
As mentioned in the $resource AngularJS Doucmentation:
Class actions return empty instance (with additional properties
below). Instance actions return promise of the action.
The statement above gives you a hint that instance action methods, $get, $save, ... and any other actions that you define in your $resource action definition, will always return a promise.
DEMO
e.g.
var User = $resource('user.json', {}, {
'update': {'method': 'PUT'}
});
var user = new User();
// this sends a GET request to user.json and returns the promise directly
// from the instance action.
user.$get()
.then(function(latestUserData) {
// latestUserData is also an instance of the User resource
return latestUserData.$update({
'name': 'Ryan'
});
})
.then(function(updatedUserData) {
// do whatever you want here
});
As you can see, the instance action $get returns a promise and when that promise is resolved, the .then() callback returns the response data from the server and at the same time it is wrapped/instantiated with the User $resource.

When do we use .$put instead of .put on an angular resource?

Say, I have the following resource:
angular.module('app.resources.demo-resource', ['ngResource'])
.service('demoResource',
function ($resource) {
return $resource('/api/path', { }, { put: { method: 'PUT' } });
});
and I want to invoke the put request, using promises later.
How do I do it correctly?
newResource.$put().then(...) or newResource.put().then(...)?
Does both variants exist and what is the difference between them?
When you define a "non-GET" action (basically, one with a method that has a request body: PUT, POST, PATCH), ngResource will automatically create corresponding actions/methods on every instance of that resource (prefixed by $). This is just for convenience.
Basically, you have two ways to call such an action:
<Resource>.action([parameters], postData, [success], [error])
or
<instance>.$action([parameters], [success], [error])
The benefit of the latter is that you don't need to specify the postData (it's the <instance> object that acts as the data).
With promises: (To the answer of #ExpertSystem)
<Resource>.action([parameters], postData).$promise
or
<instance>.$action([parameters])
is automatically a promise. The first approach looks more stable in practice. (Angular resolved an instance to the wrong resource after I did _.extend({}, resource))

How to mock get(id) requests

I am building an application prototype and try to mock the REST web-services.
Here is my code:
var mock = angular.module('mock', ['ngMockE2E']);
mock.run(function($httpBackend){
users = [{id:1,name:'John'},{id:2,name:'Jack'}];
$httpBackend.whenGET('/users').respond(users);
$httpBackend.whenGET(new RegExp('\\/users\\/[0-9]+')).respond(users[0]);
}
Everything is ok, my resource User.query() returns all users, and User.get({id:1}) and User.get({id:2}) returns the same user (John).
Now to improve my prototype, I would like to return the appropriate user, matching the good id.
I read in the angular documentation I should be able to replace the RegExp URI by a function. The idea is to extract the id from the url to use it in respond method.
I then tried this:
$httpBackend.whenGET(new function(url){
alert(url);
var regexp = new RegExp('\\/users\\/([0-9]+)');
id = url.match(regexp)[1];
return regexp.test(url);
}).respond(users[id]);
The problem is the url parameter is always undefined. Any idea to achieve my goal?
By using new function(url) your app tries to instantiate a new object from your anonymous function and pass that new object as the first argument of the $httpBackend.whenGET() call.
Of course, at the time of calling whenGET() no URL is provided, thus it is always undefined.
You should pass the function itself (and not an object instanciated using the function). E.g.:
$httpBackend.whenGET(function (url) {
...
}).respond(users[id]);
UPDATE:
After some more digging it turned out that the option to pass a function as the first argument to whenGET was added in version 1.3.0-beta.3. The docs you were reading probably referred to the latest beta version, while you were using an earlier version.
(Note that even versions 1.3.0-beta.1 and 2 did not provide this option.)
Without getting into much detail, responsible for verifying a matching URL is MockHttpExpectation's matchUrl method:
function MockHttpExpectation(method, url, data, headers) {
...
this.matchUrl = function(u) {
if (!url) return true;
if (angular.isFunction(url.test)) return url.test(u);
if (angular.isFunction(url)) return url(u); // <<<<< this line does the trick
return url == u;
};
The line if (angular.isFunction(url)) return url(u); is the one that gives the option to directly pass a function and was added in version 1.3.0-beta.3 (as already mentioned).
But, if you still want to pass a function to a previous AngularJS version, you could "trick" angular into believing you passed a RegExp, by providing an object with a test method.
I.e. replace:
.whenGET(function (url) {...})
with:
.whenGET({test: function (url) {...}})
See, also, this short demo.
I found a solution by using a function in the respond part instead of the when part:
$httpBackend.whenGET(new RegExp('\\/users\\/[0-9]+')).respond(
function(method, url){
var regexp = new RegExp('\\/users\\/([0-9]+)');
var mockId = url.match(regexp)[1];
return [200, users[mockId]];
}
});

Testing for whether an object is an angularJS $resource

Simple (seeming) question - I'm trying to do a simple sanity check in my AngularJS controller to make sure that my $resource is actually instantiated as such. It's a largish app, but for example:
.factory('AccountSearchService_XHR', ["$resource", function($resource) {
var baseUrl = "http://localhost\\:8081/api/:version/accounts/:accountNumber";
return $resource(baseUrl,
{
version: "#version",
accountNumber: "#accountNumber"
},
{
get: {method: 'GET', isArray: false}
});
}]);
Then later, in controller:
$scope.accountObj.currentAccount = AccountSearchService_XHR.get({
version: "v1",
accountNumber: "1234"
},
function(result) {... etc etc});
The call to my API works fine, everything returns data like I expect - but I'd like to test to see if $scope.accountObj.currentAccount is a Resource before trying to make the .get call (notice the super important capital "R").
When I inspect the object $scope.accountObj.currentAccount in chrome debugger, it looks like:
Resource {accountHolderName: Object, socialSecurityNumer: null, birthDate: "05/14/1965", maritalStatus: ...}
Because of some complexity in my setup though, occasionally it gets overwritten as a normal object (typeof returns "object"), but inspecting it in debugger confirms it lost its Resource status.
So - does anyone know of a way to test whether it is a $resource? Almost like typeof $scope.accountObj.currentAccount returns "Resource"? Or perhaps a better best practices way to ensure that things are connecting up all proper and respectable-like?
All the SO articles I have seen when searching revolve around actual Jasmine testing.
Thanks in advance.
#tengen you need to have injected the type you want to check against, instead of $resource.
All resources are instances of the "class" "Resource", but that's a function that's defined inside of the factory method of the $resource service, so you have no outside visibility to use it with the instanceof operator.
However, you're wrapping that $resource creating with your own custom type, AccountSearchService_XHR, and that's what you need to make the check against.
You need AccountSearchService_XHR to be injected in your code and then perform myRef instanceof AccountSearchService_XHR and that will be === true.
Digging up an old question my intern just had. The simple solution is:
if ($scope.accountObj.currentAccount instanceof AccountSearchService_XHR)
return 'This is a AccountSearchService_XHR Resource';
else
return 'This is not a AccountSearchService_XHR Resource';
which with proper names (Users being a $resource) and real case scenario should lead you to write something like this:
if (!(this.user instanceof Users))
this.user = new Users(this.user);
this.user.$update();
Check it via instanceof yourVariable === "Resource". Because Resource is an object the type will always return as an Object, but if you check that it's an instance of the Resource "class" that should work just fine.

Resources