Easily query and update your JSON API using angular.js
.
Inspired by ngResource, I wanted to create a service that can manage the data retrieved from an API and always return the same objects, so Angular will automatically update all your views.
var Messages = Collection.new("Messages", {url: "/messages"});
Messages.all()
// => {Array all, $promise: {Function then}, ...}
Messages.get({id: 42})
// => {Object data, $promise: {Function then}, ...}
Grab the source code, e.g. by using bower, which will fetch the latest release tag and all dependencies:
$ bower install killercup/angular-epicmodel
Include all dependencies and EpicModel in your build process or HTML file. (Choose one of dist/model.js
, dist/model.min.js
or src/model.coffee
.)
Add EpicModel as a dependency to the angular modules that use it:
angular.module('DemoApp', ['EpicModel'])
.run(function (Collection) {
console.log("Did we load EpicModel?", !!Collection);
});
- Angular.js (~1.2)
- Lodash (~2.4)
I'll try to document as much of the Collection API as possible, but if you want to know what really happens, you should read the (well-documented) source code in src/model.coffee
. Additionally, the tests may give you a nice overview of some the possibilities.
- Creating a Collection Instance
- Collection Options
- Using API Data
- Adding Additional API Methods to a Collection
- Add a Method
- Add a HTTP Call
- URL Formatting
- Global Configuration
To describe an API endpoint, you instantiate a new instance of a Collection
using the new
method. (Please note, that you might want to call it using Collection['new']
if you need to support ECMAScript 3 in old browsers.)
Options:
- name (string) for the collection. The URL may be derived from this (e.g. a collection called "Users" has the default URL "/users").
- A config object with options like
url
,detailUrl
and is_singleton (more info see below) - An extras object for additional API methods (for more info see below)
This option can be set when creating a collection:
- url (string or function): The URL for the list resources (e.g.
/users
) - detailUrl (string or function): The URL for the detail resources (e.g.
/users/42
). Default is[list_url]/[entry.id]
. See URL Formatting for information on how to change this. - baseUrl (string) to overwrite the global API base URL
- is_singleton (boolean): Set to true when the resource is not a list of entries, but just a single data point, e.g. "/me" (as an alias for requesting the current user).
- matchingCriteria (function): Specify how to match entries (e.g. to update an old representation). The default is to use
entry.id
. If you specify you own function, it should return an object that can be used in lodash'swhere
method. E.g., to use MongoDB's_id
field as an identifier, use this a a matching function:function (item) {return {_id: item._id};}
Each Collection instance has the default CRUD methods available as all()
, get({id})
, create(data)
, update({id}, data)
and destroy({id})
.
Whereas create
, update
and destroy
return promises, all
and get
return objects with special keys so they can be used directly in your view.
var Posts = Collection.new("Posts");
var posts = Posts.all();
// => {
// all: undefined,
// $promise: {then()},
// $loading: true,
// $resolved: false,
// $error: false
// }
As you can see, the actual data is stored in the all
property (or data
for get
and other calls). Your typical view would look like this:
<p ng-show="posts.$loading">Loading</p>
<p ng-show="posts.$error">Oh noes!</p>
<ul ng-repeat="post in posts.all">
<li>{{post.title}}</li>
</ul>
When API response is received, it will be used to update that object's properties, so that angular's dirty checking can detect a change and update your view automatically.
This has several advantages. Since EpicModel stores all API data in an internal cache, it can set the posts.all
value to all the posts it has already cached initially. When the new list of posts is received, it can then update the internal cache, adding new entries and updating existing ones. These updates will immediately be reflected in your view.
Even better: Since the objects themselves are always the same ones, even if you have several views displayed each showing a filtered list of posts from this Collection, they will all be updated. You don't even have to use track by
in your ng-repeat
!
To add more methods to your collection, specify them as keys of the extras object when creating the collection. Their values can either be functions or objects.
Below, three different variants will be shown. For more information (e.g. onSuccess
or onFail
transforms), have a look at the extras tests or the extras implementation.
The easiest case: Each extras property that is a function will become static method of your collection.
var options = {};
var extras = {
isNew: function (msg) {
return true;
}
};
var messages = Collection.new("Messages", options, extras);
messages.isNew({id: 21})
// => true
To make methods more powerful, they are bound to the collection configuration, i.e. this
in your method will be the object with all configuration options set during the collection creation.
Please note that some helpful methods not documented in the configuration section above are also part of this object, e.g. config.getDetailUrl(entry)
and config.Data = {get(), replace(), updateEntry(), removeEntry()}
.
You could use static methods to make HTTP calls, but EpicModel offers an easier alternative: Just specify an object with HTTP options.
var options = {};
var extras = {
markUnread: {
method: 'PUT',
url: '/messages/{id}/mark_unread',
params: {
skip_return: true
},
data: {
'read': false
}
}
};
var messages = Collection.new("Messages", options, extras);
var message = {id: 42};
var httpCall = messages.markUnread(message);
// => PUT /messages/42/mark_unread?skip_return=true
httpCall
.then(function (response) {
console.log(response.data.read);
// => false
})
.then(null, function (error) {
console.log(error.status);
// => e.g. 404
})
The only option you need to set is the HTTP method using method
. If the URL cannot be guessed from the property key, you can overwrite it using url
(cf. URL Formatting). Additionally, you can specify all the options $http can process.
Calling a method specified like this will return a promise that settles with the HTTP response, just like using $http.
One of the clever things in EpicModel is how it allows you to set detail URLs.
When you specify a URL as a string, you can use curly braces to include some values from the entry you are requesting, e.g. /users/{id}-{name.first}_{name.last}
.
When you specify a function, it will be called with the information about the entry you are requesting, the API's base URL and the list URL (from config.url
). It should return string.
You can inject the CollectionProvider
into your module's config
to set the following options:
- API Base URL (string)
angular.module('app', ['EpicModel'])
.config(function (CollectionProvider) {
CollectionProvider.setBaseUrl("http://localhost:3000");
});
MIT