⚠️ This lesson is retired and might contain outdated information.

Using $resource for Data Models

Brett Cassette
InstructorBrett Cassette
Share this video with your friends

Social Share Links

Send Tweet
Published 10 years ago
Updated 2 years ago

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.

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.

Brett Cassette
Brett Cassetteinstructor
~ 10 years ago

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!

John
John
~ 10 years ago

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.

Brett Cassette
Brett Cassetteinstructor
~ 10 years ago

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 :)

Brett Cassette
Brett Cassetteinstructor
~ 10 years ago

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 :)

David
David
~ 10 years ago

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!

Dylan
Dylan
~ 10 years ago

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!

Igor
Igor
~ 10 years ago

@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

Brett Cassette
Brett Cassetteinstructor
~ 10 years ago

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

Brett Cassette
Brett Cassetteinstructor
~ 10 years ago

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 :)

Brett Cassette
Brett Cassetteinstructor
~ 10 years ago

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 :)

Dmitri
Dmitri
~ 9 years ago

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.

Brett Cassette
Brett Cassetteinstructor
~ 9 years ago

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

Dmitri
Dmitri
~ 9 years ago

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.

Brett Cassette
Brett Cassetteinstructor
~ 9 years ago

I think that's a good choice Dmitri :)

Brett Cassette
Brett Cassetteinstructor
~ 9 years ago

I think that's a good choice Dmitri :)

milad
milad
~ 9 years ago

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 !

Jonah
Jonah
~ 8 years ago

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?

Joel Hooks
Joel Hooks
~ 8 years ago

It's there, in main.js.

Markdown supported.
Become a member to join the discussionEnroll Today