A layer to abstract the data transport on higher level components (controllers, directives...).
This library implements a thin layer of abstraction between service endpoints and consumers who need to do data manipulation (CRUD). It is best used with RESTful backend providers.
The actual communication with any kind of service is done with a DataProvider
, which implements
the methods defined in the DataProviderInterface
.
A sample provider can be seen here
Each method has the responsibility of do server calls and return a Promise
. The methods are:
- find(endpoint, id)
- findAll(endpoint, parameters)
- save(endpoint, entity)
- saveAll(endpoint, entitySet)
- remove(endpoint, id)
- removeAll(endpoint, ids)
Repository
is the actual top-level API consumable by other parts of the app. It has the following methods:
- find(id)
- findAll(query)
- save(entity)
- saveAll(entityArray)
- remove(id)
- removeAll(ids)
It all starts with the RepositoryManager
, where a Repository
is registered.
You must register a repository to each resource existing in the backend.
To ensure the configuration is correct, you must create a RepositoryConfig
instance to define the
repository parameters. Each repository performs some operations using a DataProvider
object, which
is passed in as a configuration to this instance.
Here's an example:
// MyBackendProvider is an extension of DataProvider that implements the communication
// User is the repository name. When you inject a repository, the name is added with a `Repository` suffix
// so the final name will be `UserRepository` for *injection only*.
// The repository can also be found with RepositoryManager.getRepository('User');
var userConfig = new RepositoryConfig({
name: 'User',
dataProvider: MyBackendProvider
});
// Here you are registering this repository
RepositoryManager.addRepository(userConfig);
The most common operations can be done directly in the repository, such as create, update, retrieve or remove entities.
The only one that has a different mechanism is the search, which is done through
context objects, called RepositoryContext
, or via QueryBuilder
objects.
The query builders are a light object containing only the parameters required to perform a search.
The RepositoryContext are an extension of queries, maintaining also the data that comes from backend whenever a parameter changes, or the last error.
Each context is created only once, and it lasts in the manager until you manually destroy it,
so you won't lose the context state on page changes (except a full refresh with Ctrl+R), whereas
QueryBuilder
instances are disposable.
Here's a context example:
function ProductListController(ProductRepository) {
var context = ProductRepository.getContext('product-list');
// if this context was not created before, initialize it
if (!context) {
context = ProductRepository.createContext('product-list');
}
// triggers a context update to get up-to-date data
context.update();
this.context = context;
}
// assuming there's a $stateParams object with the productId to edit
function ProductEditController(ProductRepository, $stateParams) {
var editor = this;
ProductRepository.find(productId).then(function(product){
editor.product = product;
});
editor.save = function() {
ProductRepository.save(editor.product);
};
}
In the list view our context object is exposed, so we have access to his state and current data:
<div class="product-list">
<div ng-repeat="product in context.data">
<h2 ng-bind="product.name"></h2>
<p ng-bind="product.description"></p>
</div>
</div>
The QueryBuilder instances are a set of chainable methods that you will use to search for things in the backend. Here's how:
var query = QueryBuilder.create()
.from('User')
// optional
.select('User.name, User.age')
.where('name', QueryBuilder.LK, 'John')
.where('age', QueryBuilder.GTE, 21)
.sort('name', QueryBuilder.DESC)
.limit(20)
.page(1);
UserRepository.findAll(query).then(function(response){
// ...
});
During the repository creation additional methods and properties can be added:
var repositoryConfig = new RepositoryConfig({
name: 'product',
dataProvider: MyDataProvider
});
var customProperties = {
updateProductStatus: function(product, status) {
product.status = status;
return this.save(product);
}
};
RepositoryManager.addRepository(repositoryConfig, customProperties);
Now if ProductRepository
is injected anywhere it will have the updateProductStatus
method
The full API of each component is listed here