README.md

EpicModel

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}, ...}

Build Status

Installation

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);
});

Dependencies

  • Angular.js (~1.2)
  • Lodash (~2.4)

Usage

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.

  1. Creating a Collection Instance
    1. Collection Options
  2. Using API Data
  3. Adding Additional API Methods to a Collection
    1. Add a Method
    2. Add a HTTP Call
  4. URL Formatting
  5. Global Configuration

Creating a Collection Instance

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)

Collection Options

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's where method. E.g., to use MongoDB's _id field as an identifier, use this a a matching function: function (item) {return {_id: item._id};}

Using API Data

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!

Adding Additional API Methods to a Collection

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.

Add a Static Method

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()}.

Add a New HTTP Call

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.

URL Formatting

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.

Global Configuration

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");
});

License

MIT