Resourcify lets you have rich data models in your angular project that rock! You can get rid of those long controllers with code that manipulates your models and move it where it belongs, into your models themselves! It even includes smart caching that you can turn on to optimize your network requests.
Other Documentation
Use bower: bower install --save resourcify
Then add to your module as a dependency
angular.module('myMod', ['resourcify']);
Resourcify lets you make a ResourcifyBuilder
that you can add all kinds of goodies onto for your backend requests and any other model manipulation you want to perform. Creating a new builder is as simple as this:
angular.module('myModule').service('Data', [
'Resourcify',
function (Resourcify) {
var UserBuilder = new Resourcify(name, url, config);
}]);
Type: string
The name of the model you are making. So for our example, lets say User
. Requred
Type: string
promise
The formatted string that will be used for any requests you add to the builder. If you need some async process to occur before you can determine what your url
needs to be, you can pass a promise that will resolved with the url
.
The url
should look like the string that angular's $resource expects for the url.
Here is an example:
http://127.0.0.1:8085/api/v1/:bob/:id/grab.:ext?yes=no&thing=:thing
All property names prefixed with :
excepting your port (if you used one) will be replaced with values coming from the instance calling a request, or from supplied parameters for the request.
In this example, we will look for a property bob
coming from passed query parameters or the model value itself (if this request has isInstance
set to true
). If bob
isn't found, then the url
will collapse to:
http://127.0.0.1:8085/api/v1?yes=no
Query parameters that are defined with values in the url will always stay. Any query params where the value is either not supplied, or can't be found on the model will be removed from the request.
Note
Values passed as query params vs values that are on the model are more aggressively added as query params. For example, with our same url, if we supplied
{bob: 'dude', ext: 'pdf'}
as query parameters andthing
andid
could not be found on the model, the url would end up being:
http://127.0.0.1:8085/api/v1/dude?yes=no&ext=pdf
Even though
ext
was a part of theurl
path, becauseid
wasn't supplied the path collapsed down to that point, and any remaning query params passed that can't be found as replacements in the url, are added as additional query params on the resulting path. Be aware of how urls are collapsed. They can be very powerful but you must understand them.
Type: object
Your config
object can contain the following properties
Type: string
Default: id
If your API responds with a Location header after a request is saved, the final piece of this url (after the last slash) is set as the id
of your model. You can set what property to use as your id
with this configuration property
Type: function
When a new instance of your model is created (either from a new
call or internally by Resourcify itself) you can run some custom setup code for your model. The this
value in your constructor
function will be the instance of the model, so you can check for the existence of values or whatever else you want to do. When Resourcify receives a response for a GET request and is expecting an object, it will call new
with the response received from the server as the paremeter and use angular.extend
to add all the properties to the model.
Type: object
To use the cache and configure it, please read jump down to the documentation
Type: boolean
Default: true
if using cache, false
if not
When making requests with your resource and with the cache disabled, you can elect to either have your requests return an object
that will eventually magically fill in with the response values from the server (a la angular's $resource), or a promise
that will resolve with the requested value. When using the cache, promises are ALWAYS returned. This cannot be changed due to the nature of caching and dealing with asynchronous requests that need to be cached.
Now that you have your builder, you can use chaining and add on requests or methods until you have just the functionality you want for your model.
UserBuilder
.request(/* config */)
.method(/* name, config */)
.request(/* config */)
You can configure any type of request using any valid HTTP Method for your Resourcify model. To add a request, with the builder in hand, use the following format:
Builder.request(config)
Your config
object can contain the following properties for each request
Type: string
The name of the request. Required
Type: string
The http
method of the request. For PUT
, POST
, and PATCH
the value of the model will be sent as the request body. Required
Type: boolean
Default: true
If this request is meant to be called from the constructor of your model rather than from the instance, you need to set this to false
.
Type: boolean
Default: false
If your request returns an array instead of an object, set this to true
so Resourcify knows and can construct an array response instead. This can be useful for requests that are run on the class level to return a list of instances. It is assumed the array contains objects that should be turned into models.
Type: string
Default: undefined
If you receive metadata with each request, and the desired data is on some property of the response, you can tell the request which property to use for your data. If that property is an array, you will still need to set isArray
to true
even though the actual response contained an object.
Type: string
promise
Default: null
If you need a slightly different url for this request than the one you set globally on the builder, you can set a url per request. You can either supply the url as a string
or a promise
that resolves with the correct url.
Type: function
A function to run before the request is made. The this
context of the function will be the model you are dealing with (either the Constructed value or the array of values depending on isArray
). You can manipulate data before it is sent to the server or run some other logic.
Type: function
A function to run after a request has completed successfully but before any registered callback or resolve occurs. If you need to do any post request processing you can do so here.
Type: boolean
Default: false
This option is to be used in conjunction with a ResourceBuilder
that is using a cache and ONLY when isArray
is true
. Setting this option to true
allows GET requests to individual values that were cached from making the request being built. Sometimes an API may return smaller optimized objects when retrieved in a list. Normally a subsequent GET to one of those individual values would just pull it out of the cache. This option allows any objects retrieved with this request method to be invalidated immediately, so a future GET request for the individual value will still hit the server.
With an instance of your model, you may need to do some manipulation at times, or you may just want to extrapolate some code away into your models. You can build a custom method
to do that:
Builder.method(name, action)
Type: string
The name of the method that will appear on the model's prototype. Required
Type: function
The function to call when your action is triggered. This function will be bound the the model's this
property to access any values or other methods you expect to be there.
When you are done with the builder, you need to create the resource.
var MyModel = Builder.create()
Once you have created the resource, you can make an angular service out of it and BOOM, ready to use.
angular.module('myModule').service('User', [
'Resourcify',
function (Resourcify) {
var UserBuilder = new Resourcify('User', ...);
// Add some requests, methods, etc.
return UserBuilder.create();
}]);
Now I can inject my User
service anywhere I want
// Inside some ctrl or directive...
$scope.newUser = new User();
// Put newUser in a form, and fill in some fields and then on submit...
$scope.newUser.save();
// Or get a list of users to show
$scope.users = User.query();
// Or if you are using cache
User.query().then(function putUsersOnScope(users) {
$scope.users = users;
});
If your app is resource hungry and you want to cut down on the requests being made, you can use the cache built into Resourcify to be more intelligent about when to make requests for resources.
To configure and setup the cache, use the cache
property when you create your Builder. In the 3rd parameter config
object, add another property cache
. This should be an object with the following options.
Type: string
array [string]
Default: ['id']
This is the property or properties on your instances that can be used to uniquely identify your objects. If your objects have an id
property that is used to uniquely identify them from your server, then set key
to the string id
. If you need ot use multiple properties to uniquely identify it, set key
to an array of strings that represent the property names to use.
Type: string
Default: 'id'
If your server returns null
on successful PUT or POST requests that create new resources and instead returns the Location
header, you can set the id
property to be the value on your objects that should represent your id (if you have one). As long as your server sends the Access-Control-Expose-Headers
header with the value Location
, then Resourcify will pull the end of this url off and set the id
property on the object that was just saved.
Type: string
Default: 'POST'
If you create new resources with requests of a different method, then you can change that here. This way the cache knows when to invalidate certain things as new things are added to the server.
Sometimes you need to clear the cache. Sometimes you want to make a request and have it go through even though you have a value cached. There are a few ways to do this.
For every request
you build, when you are using the cache there is an extra request created to 'force' a request. If you created a request called query
and you want to bypass the cache and force an $http request to the server, you can call Model.query.force
and use it the same as the normal Model.query
.
Each Resourcify model keeps track of every object and list that has been retrieved using its request methods. For every unique object you have retrieved from the server or saved to the server, there exists only one copy of it ever in memory. If you use a request to get a specific instance of a model on one page, and do that again on another page, you can be sure they are indeed the same object. If you want to clear the cache for a specific object, you can call the function $clearCache
that has been added on to it by Resourcify. If you want to invalidate ALL objects for a specific model and all query requests with any combination of query params, you can call $clearCache
from the Model constructor itself.
// Some instance of a user
user.$clearCache();
// This request will get to the server
user.$get();
// Let's clear cache of all users anywhere in our app
User.$clearCache();
If you have resources that use nested URLs, you can save time by nesting resources.
Say you have these two Resourcify Objects:
var User = new Resourcify('User', 'http://localhost/api/users/:userId')
.request({method: 'GET', name: 'query', isArray: true})
.create();
var Comment = new Resourcify('Comment', 'http://localhost/api/users/:userId/comments/:commentId')
.request({method: 'GET', name: 'query', isArray: true})
.create();
The URL for Comment
is dependent on User
. Or in other words, Users have comments and can create/modify them using their own namespace. Let's nest the Comment
resource under User
so that we can more easily create and find comments for a given user.
var Comment = new Resourcify('Comment', 'http://localhost/api/users/:userId/comments/:commentId')
.request({method: 'GET', name: 'query', isArray: true})
.request({method: 'POST', name: '$save', isInstance: true})
.create();
var User = new Resourcify('User', 'http://localhost/api/users/:userId')
.request({method: 'GET', name: 'query', isArray: true})
.subResource(Comment)
.create();
Now when you make a new instance of User
, it will have a unique reference to the Comment
constructor on it. For example.
var user = new User({
userId: 123,
name: 'Bob'
});
// This user can now create new Comments that are tied to it
var comment = new user.Comment();
// You can add some properties to it...
comment.text = 'Yo bro';
comment.tags = ['fun', 'exciting', 'wow'];
// And then when you save it, it will use the user object that
// created it to fill in the `userId` property
comment.$save(); // POST to http://localhost/api/users/123/comments
If your objects all use the property id
as the unique identifier, you can tell Resourcify how to map the nested objects parameters
// With url for Comment as http://localhost/api/users/:userId/comments/:id
var User = new Resourcify('User', 'http://localhost/api/users/:id')
.subResource(Comment, {userId: 'id'})
.create();
// Here both Comment and User refer to themselves by `id`
// We tell the User resource that when making nested Comments
// to fill in the `userId` property on the Comment url with the
// `id` property on the User.
This also works if you nest multiple objects down at multiple depths. If you do this with 3 or more layers however, you need to let Resourcify know at which layer should it use the given property from the parent to fill the desired attribute
Say we had a Tag resource on our comment
var Tag = new Resourcify('Tag', 'http://localhost/api/users/:userId/comments/:commentId/tags/:id');
var Comment = new Resourcify('Comment', 'http://localhost/api/users/:userId/comments/:id')
.request({method: 'GET', name: 'query', isArray: true})
.request({method: 'POST', name: '$save', isInstance: true})
.subResource(Tag, {userId: 'id@2', commentId: 'id@1'})
.create();
You can use the <propName>@<num>
syntax when you need to reference multiple properties with the same propName. You just need to tell Resourcify at what depth to find it. In our case use the id
property 2 levels in (on the User) to find the userId and use the id
property 1 level down (on the Comment) to find the commentId
.