Enter Your Email Address to Watch This Lesson

Your link to unlock this lesson will be sent to this email address.

Unlock this lesson and all 833 of the free egghead.io lessons, plus get Angular 1.x content delivered directly to your inbox!



Existing egghead members will not see this. Sign in.

Just one more step!

Check your inbox for an email from us and click link to unlock your lesson.



Using $resource for Data Models

5:29 Angular 1.x lesson by

AngularJS's $resource service allows you to create convenience methods for dealing with typical RESTful APIs. In this video, Brett will show you the basics of using $resource, as well as talking about some of the drawbacks with using this service for your data models.

Get the Code Now
click to level up

egghead.io comment guidelines

Avatar
egghead.io

AngularJS's $resource service allows you to create convenience methods for dealing with typical RESTful APIs. In this video, Brett will show you the basics of using $resource, as well as talking about some of the drawbacks with using this service for your data models.

Avatar
John

_.remove($scope.posts, post)} will remove from $scope.posts, but what if i'm using Posts in another controller? After calling an update, insert, or delete, what is the best approach for updating all controllers that are using the Post factory ?

My thinking is we need to have a callback within the factory that calls Post.query() again, but would that trigger the controllers to update their models?

In reply to egghead.io
Avatar
Brett Shollenberger

Excellent question. This is among the shortcomings of working with $resource.

Short answer: I've written a library that deals with modeling. You could simply use that (https://github.com/FacultyCreative/ngActiveResource). This functionality is baked into the search functionality in my library.

Long answer (and I'll be going through more of this in detail as we examine how to build better models than $resource):

To manage multiple resources across controllers, we need to extend the concept of collections. When we have associated collections ($scope.post.comments), the collection itself belongs to the instance (post), and not a particular scope. If we used that same code:

_.remove($scope.post.comments, comment);

It would be updated across scopes. Directives like ng-repeat will also pick up on the change; no need to $scope.apply. That's fairly easy, because it's like we have a single subscriber--the owner of the comment.

Let's say, instead, you're dealing with the top-level resource, e.g. $scope.posts. You use the $resource.query method, and end up with an array that doesn't remove the instances you deleted from it across scopes. Why is that?

All Javascript objects are stored by reference, and there is no way to delete the actual object on the heap, only a pointer to the particular reference. The object will be garbage collected by Javascript when there are no longer any additional references to that object. So as long as it remains in some other array somewhere, it will not be garbage collected or removed from that array.

That leaves us with a few options in Angular. We could iterate through all $scopes, and remove any references to the object we come across. That sounds super expensive, and isn't a great option for us.

The option I think makes the most sense is to unify the interface through which you'll create instances. In $resource, that means the query method, and it necessarily means overriding it. We can create a pub/sub interface on all arrays created and returned by query; when we delete instances on the future, we can loop through each of our subscriber arrays and remove the instance. Again, since Javascript stores only references to objects on the heap, updating the object in a single location will update it in all locations--without calling $scope.apply, because we've updated the core object itself. We must create this pub/sub interface, because otherwise we'd have no means of tracking the pointers:

var originalQuery = angular.copy(Post.query);
Post.watchedCollections = [];

Post.query = function() {
        var results = originalQuery();
        Post.watchedCollections.push(results);
        return results;
}

var originalDelete = angular.copy(Post.delete);

Post.delete = function(instance) {
        originalDelete(instance);
        delete instance['$$hashKey']
        _.each(Post.watchedCollections, function(watchedCollection) {
          _.remove(watchedCollection, instance);
        });
}

If you create your own custom querying methods using $resource (anything with isArray: true set), you'll also want to add those results as subscribers, too.

Hope that helps!

Avatar
John

To manage multiple resources across controllers, we need to extend the concept of collections. When we have associated collections ($scope.post.comments), the collection itself belongs to the instance (post), and not a particular scope.

Thanks for the response! I've seen this demonstrated in an early egghead video. My thinking is, if we can do this, why not just invent an object to stick the posts on, like $scope.shared.posts? I tried something like that, and it didn't work.

All Javascript objects are stored by reference, and there is no way to delete the actual object on the heap, only a pointer to the particular reference. The object will be garbage collected by Javascript when there are no longer any additional references to that object. So as long as it remains in some other array somewhere, it will not be garbage collected or removed from that array.

Just checking if I follow this:
So Post.query() puts an object on the heap, and in our controller we assign $scope.posts to reference that object. Assuming we have two controllers that are both loaded and have both have called $scope.posts = Post.query() -- that means we have 2 references to the same object? So when we call Post.delete() a new object is created by $resource. Both controllers still have reference to the old one. Not a problem for the controller that called Post.delete(), we can just set $scope.posts to refer to the new object, but the other controller is still referring to the old one, and is left hanging in the dust?

The option I think makes the most sense is to unify the interface through which you'll create instances. In $resource, that means the query method, and it necessarily means overriding it. We can create a pub/sub interface on all arrays created and returned by query; when we delete instances on the future, we can loop through each of our subscriber arrays and remove the instance. Again, since Javascript stores only references to objects on the heap, updating the object in a single location will update it in all locations--without calling $scope.apply, because we've updated the core object itself. We must create this pub/sub interface, because otherwise we'd have no means of tracking the pointers:

So instead of looping through all $scopes which you explained is expensive, we loop through only the subscribers, a shorter and complete list (no waste)?

Look forward to more $resource videos in the future, especially ones that deal with best practices for handling async gracefully and transparently to the user for the most 'desktop' like experience possible.

In reply to Brett Shollenberger
Avatar
Jose Luis Monteagudo

Hello,

I would like to expose a problem that I'm facing sending complex objects as parameters to ngResource. I'm trying to do the following query with ngResource (I know I shouldn't do this in my controller, but it's only a spike):

var Customer = $resource('http://localhost:3000/api/users');
        var params = 
        {
            conditions: {
                age: { "$gt": 30 }
            },

            options: {
                limit: 2
            }
        };

        Customer.query(params, 
            function(data) {
                $scope.users = data;
            }
        );

And in the browser console I get the following error:

GET http://localhost:3000/api/users?conditions=%7B%22age%22:%7B%7D%7D&options=%7B%22limit%22:2%7D 400 (Bad Request)

As you can see, the parameters that I'm sending are being escaped, and this is causing a bad request.

If I try to do the following request through $http I don't get any problem:

$http.get('http://localhost:3000/api/users?conditions={"age":{"$gt":30}}&options={"limit":2}').success(function(data) {
                    $scope.users = data;
        });

So, I have two questions:

1) Is there anyway to avoid the problem that I'm facing when sending complex parameters to ngResource.

2) Do you think that is a good practice sending parameters as I'm doing in my previous examples? Or do you think that would be better in this way: http://localhost:3000/api/users?age-gt=30&limit=2 ? Undoubtedly, this last way is much cleaner, but I think that in my previous examples I have a lot of flexibility, because this is the way that MongoDB uses to query its collections, and as I have a MongoDB in my back-end then I can send directly the parameters to the MongoDB query.

I didn't like brackets in the URL, but I have seen that parse.com uses a similar syntax to query its data:

curl -X GET \
  -H "X-Parse-Application-Id: ${APPLICATION_ID}" \
  -H "X-Parse-REST-API-Key: ${REST_API_KEY}" \
  -G \
  --data-urlencode 'where={"score":{"$gte":1000,"$lte":3000}}' \
  https://api.parse.com/1/classes/GameScore

I would appreciate a lot your comments regarding these two questions.

Thank you very much!!

In reply to egghead.io
Avatar
Brett Shollenberger

So Post.query() puts an object on the heap, and in our controller we assign
$scope.posts to reference that object. Assuming we have two controllers that are
both loaded and have both have called $scope.posts = Post.query() -- that means
we have 2 references to the same object? So when we call Post.delete() a new
object is created by $resource. Both controllers still have reference to the old one.
Not a problem for the controller that called Post.delete(), we can just set
$scope.posts to refer to the new object, but the other controller is still referring to
the old one, and is left hanging in the dust?

Not by default. By default, every time you query, the result returned is a new array. Even if its contents are 100% identical to the previous query, they are not identical objects. That's why I track special arrays in ActiveResource (like post.comments), and why we need to wrap Post.query in the other example I gave :)

In reply to John
Avatar
Brett Shollenberger

Hey Jose-
I've run into this issue before with MongoDB, but I haven't specifically written an adapter to deal with it. My general thoughts are that $resource's url encoding is a feature, not a bug. They're suggesting a particular approach to API design, which can be challenging because it's the wild west in the wonderful world of APIs.

So I wouldn't say you need to avoid this challenge unless you don't have control over your API, in which case, I would write a method to parameterize the query string in the way you showed in your first example. You're exactly right, you'll have greater flexibility working with the grain of MongoDB's query interface. While your second method is clean, it's a proprietary API. It's likely developers working on your app will know MongoDB, but they'll absolutely have to learn your own API if you write something different.

If you do have control over your API, I would decode the URL on the backend, and use the request generated by $resource. Many of the characters in the request are reserved and ought to be percent encoded.

Be careful how you expose these endpoints. You don't want just anyone to be able to query arbitrarily into your database :)

In reply to Jose Luis Monteagudo
Avatar
Brett Shollenberger

Thanks for the response! I've seen this demonstrated in an early egghead video.
My thinking is, if we can do this, why not just invent an object to stick the posts on,
like $scope.shared.posts? I tried something like that, and it didn't work.

This is a good idea. You need to make sure you're always assigning the posts to that exactly array. Query creates a new array by default, so you need to hook into that method, and write any new instances to your shared array.

If you assign the variable $scope.shared.posts to a different array, recognize that now the pointer has switched to a different array, not changed the value of the previous array.

The approach you've described is the approach I showed in the previous example, applied in a different way. See if you can use that example to figure out how to do this :)

In reply to John
Avatar
Jose Luis Monteagudo

Hello Brett,

Thank you for your response.

I have already seen where is the problem although I have not resolved it yet. I'm sending the following condition to my endpoint:

...
conditions: {
                age: { "$gt": 30 }
            },
...

The problem is that when Angular sends that request to my API endpoint, Angular removes those fields that starts with $. So Angular sends the following information to my endpoint: conditions={age:{}} If I send this information to MongoDB then it returns an error.

I dont' know still how to avoid this problem. Maybe it's easier to use my own propietary API, like in this way: http://localhost:3000/api/users?age-gt=30&limit=2

Best regards!

In reply to Brett Shollenberger
Avatar
Brett Shollenberger

You're right, Jose. Creating your own API to work with $resource could solve this scenario, but I think you would get better mileage out of either wrapping $resource and changing it to allow your $ arguments through than you would parsing \w-gt on the backend, since it would allow you to stick to MongoDB conventions on the frontend. Either way, I think you're on the right track solving this.

In reply to Jose Luis Monteagudo
Avatar
David

Hi Brett, great video! Do you have any idea when you plan to release the video on creating a "more robust ORM?" Could I sign up to be emailed when that becomes available somehow, or can you publish a notification in this thread so I get the subscribe message?

Does ngActiveResource address any of those ORM concerns, or is that separate?

Thanks and keep up the good work!

Avatar
Dylan

If I understand correctly, your argument against using $resource for Data Modeling is twofold: sharing collections across $scopes is unintuitive and potentially dangerous, and it's tough to define "model-ly" things like behavior and relational mapping.

The complications of sharing collections are certainly a drawback to using just $resource for modeling. It begins to violate Single Responsibility; $resource is inherently Entity and Entity Repository (not a huge deal), but adding something like Post.all or Post.current adds concerns traditionally assigned to Managers, Caches, Mappers, or other encapsulated system actors.

However, in re: "One of the most conspicuous areas where $resource is lacking is its ability to let us create business logic...", it's worth pointing out that $resource can be effectively used to shape a model, map its relationships, and to define its behaviors. We can leverage prototypical inheritance as described in Mastering Web Application Development with AngularJS, e.g.:

var Post = $resource(//...);
Post.prototype.comments = [];
Post.prototype.addComment = function (comment) {//...}

That being said, you'd be tightly coupling your entire domain model---not just to Angular, but also to one specific Angular service. I'll be very interested to see your ORM progress, because I haven't come across many efforts to handle this unavoidable application concern in a way that is idiomatically "Angular". Great stuff!

Avatar
Igor

@Brett could there be a simpler wrapper around $resource which provides model specific behavior.

Here is one I came up with (thanks SO for ideas): http://stackoverflow.com/questions/23528451/properly-wrap-new-domain-object-instance-with-resource/23529358#23529358, but I'd be curious is there is more concise/clearer method.

Igor

Avatar
Brett Shollenberger

Hey David-
The videos are a part of this series, which is shaping up to be quite a long one. The topics covered are rather large and diverse, and I want to do justice to the complexity of each. At the time of this writing, we're spending a lot of time on component pieces that are being used to compose the system as a whole.

-Brett

In reply to David
Avatar
Brett Shollenberger

Hey Dylan-
Sorry my responses have been so long coming. You're correct about my concerns with Angular's modeling capabilities out of the box. I'll be quite interested to see what the truly "Angular" approach is when the core team release v2, which is slated to include a new modeling library last I heard.

We'll be working through many challenges not covered in the current ngActiveResource as we proceed. After iterating through many pre-1.0 releases, I have a lot of new ideas up my sleeve for the series :)

In reply to Dylan
Avatar
Brett Shollenberger

Hey Igor-

The thing to remember about Angular is it's just Javascript :P Here's a list of "classical" inheritance patterns in Javascript from Douglas Crockford, some spins of which have been shown in this series. My personal favorites are the "more promiscuous" (as Crockford calls them) styles of multiple inheritance that are popular in the Ruby world, and are covered in the Inherits/Extends video ( https://egghead.io/lessons/angularjs-refactor-the-model-base-class-with-mixins). Please share other ideas you come up with in the thread :)

In reply to Igor
Avatar
Dmitri

Can you PLEASE shed some light on the future of ngActiveResource project. There haven't been any updates since April 2014 and we afraid to pick it as it looks abandoned. Thank you very much.

Avatar
Brett Shollenberger

Hey Dmitri-

I would view ngActiveResource primarily as a learning tool. When I write front-end code, I do use it, but I also feel very comfortable working through new features as I require them.

There is a much more up-to-date version than the one you see (end of last year), but I have trouble getting my old company to merge my new code to the old codebase, so I've been maintaining another copy here (https://github.com/brettshollenberger/the-abstractions-are-leaking).

Either way, my day job is now primarily server-side, and I only sparingly have need to work on the project. I would love to see some new maintainers step up, but until such a time arises, I would view the codebase as a way to learn about the concepts of data modeling, and use a more actively maintained project.

Best,
Brett

In reply to Dmitri
Avatar
Dmitri

Thank you Brett. The team was too concerned about using the unsupported library in prod app, despite how attractive it looked. We went with js-data (http://www.js-data.io) instead.

In reply to Brett Shollenberger
Avatar
Brett Shollenberger

I think that's a good choice Dmitri :)

In reply to Dmitri
Avatar
Brett Shollenberger

I think that's a good choice Dmitri :)

In reply to Dmitri
Avatar
milad

I hate it , I truly hate it when you're speaking fast and coding fast without really explaining what's happening .
It's like you wanna pee all the time so you're coding fast !

Avatar
Jonah

Hello, can you provide the source code for app.js? I assumed it was just something like this..

angular.module('app', []);

.. but I am getting an unknown provider error.

Also did I miss the setup for the api? Is that in another course?

Avatar
Joel

It's there, in main.js.

In reply to Jonah

Brett Shollenberger: Let's take a look at one way we can build models in Angular. We're going to build a post model using the built-in $resource provider. The first thing that we pass it is the API where we can grab posts, and we can parameterize it with the _id for MongoDB. The second parameter is an empty object because there are no default parameters. There's no default ID.

We can add arbitrary methods here. We'll add an update method because it doesn't come with that out of the box, and we'll probably want to send put requests. The last thing we do there is to return the model.

We've got a post model. Let's see what we're going to build with it. I want to build an interface that lets us add posts. It will let us edit posts, like this. We can refresh. We see everything's still there. And we can delete posts.

That's what we're going to build. Let's see how we're going to build it. We're going to pass in our post model, and we can start using it. We can add post to the DOM. That will be a new post that's going to be the post that we enter text in and save.

We can add posts to the DOM. We'll do that by querying the back end for all of the posts. That's given to us by $resource. We can add a save function that will be bound to the DOM as well.

Here, we're going to save whatever the current post is. That's what we're adding text into. Then we can push that post into the post array. Finally, we'll set scope post to a new post so that the form clears and we can enter a new post.

We can create delete basically the same way. This is another function we're adding to the DOM. We actually need to call post.delete. That's on the model. We'll pass in the post. We'll pass that in on the DOM.

Then we will remove this using lodash. We really should be waiting for a response from the back end to make sure that it's good to go ahead and remove it. For right now, we'll just do this. Looking good.

Let's take a look at what our DOM looks like here. We can add some fancy elements to it, like ng-show on the title and on the table, with post.length. As long as that is not equal to zero, those guys will show up.

We're doing an ng-repeat for that table to create each of the rows. We have things like active post, which is a method we haven't created yet, that will make the post. We click on the active post so that we can edit it.

We add the delete click event on that delete button. We can add an editing property to the scope that will tell us whether or not we're editing the post and change this title appropriately.

On our form, as we might imagine, we have an ng-submit with a save function. We bind ng-model to the current post. Remember, that's going to be this guy, the new post.

Our submit button, we don't even need to tell it that it has this ng-submit on it, because automatically it will bind to that. We add this new post button, if we're editing another post, so that we can clear the form there.

I won't waste your time writing the rest of these methods on film. You can take the rest of them from the notes and play with them yourself.

One of the big things to notice is that our controller's already getting pretty hefty. It's going to get unmanageable, given that we're going to have to do this for every single model. A lot of these things should be built into the model itself, like whether or not we should update or save based on whether or not we have an ID, whether or not we should add new posts to the post array.

Here, again, I haven't even added functionality to it that says, "If we get a valid response back from the back end, then we should add it." We should have a lot of this stuff taken care of for us, and $resource really doesn't fit the bill there.

One of the most conspicuous areas where $resource is lacking is in its ability to let us create business logic, which is something we'd normally associate with a constructor like we're building here.

We can start to overcome that by actually going ahead, building a constructor, and assigning methods that we would get from $resource, which I'm calling just the post methods right now, either to the instance or to the post constructor itself, which is what we'll have to do for something like query, delete, and all those other guys.

We'd still be missing a lot of functionality that we're going to need on every single model. We're going to need relationships. A post probably has many comments. We probably want to eagerly load them when we load our post. A post probably has validations. We shouldn't save a post if it's not valid. We should show the validations to the user on all of our forms, without having to add directives for all those things specifically.

In future videos, I'd like to explore how we can actually create a more robust ORM. Look out for that, you guys.

HEY, QUICK QUESTION!
Joel's Head
Why are we asking?