Collections hold an observable array of models along with any related metadata. Normally they will be used represent a collection of resources from the API.
A simple use case for a collection would a list of aprojects. Below is a JSON representation of a projects collection returned from a JSON:API compliant endpoint.
{
"meta": {
"totalRecords": 2,
"totalPages": 1
},
"links": {
"self": "http://example.com/projects?page[number]=1",
"first": "http://example.com/projects?page[number]=1",
"next": null,
"prev": null,
"last": null
},
"data": [
{
"id": "1",
"type": "projects",
"attributes": {
"name": "Sample Project 1",
"project_number": "12345",
"start_date": "2017-01-12T03:44:00.433Z",
"end_date": "2017-01-12T03:44:00.433Z",
"status": "active",
"time_zone": "PDT"
},
"relationships": {
"company": {
"data": {
"type": "companies",
"id": "1"
}
}
},
"links": {
"self": "http://example.com/projects/1"
}
},
{
"id": "2",
"type": "projects",
"attributes": {
"name": "Sample Project 2",
"project_number": "12346",
"start_date": "2017-01-12T03:44:00.433Z",
"end_date": "2017-01-12T03:44:00.433Z",
"status": "active",
"time_zone": "PDT"
},
"relationships": {
"company": {
"data": {
"type": "companies",
"id": "1"
}
}
},
"links": {
"self": "http://example.com/projects/2"
}
}
]
}
extend
To create a Collection class of your own, extend Collection, providing instance properties, as well as optional classProperties to be attached directly to the collection's constructor function.
import { Collection } from 'mobx-jsonapi';
class ProjectsCollection extends Collection {
// My properties and methods
}
model()
Override this method to specify the model class that the collection contains. If defined, you can pass raw objects (and arrays) to the add()
and create()
actions of the collection and they will be converted into a model of the proper type.
import { Model, Collection } from 'mobx-jsonapi';
class Project extends Model {
type = 'projects'
};
class ProjectsCollection extends Collection {
model() {
return Project;
}
}
url()
Override this method to specify the URL endpoint to use for CRUD operations. This will be used to fetch and create new models on the server. All models added to the collection will use this as their base URL (unless explicitly overridden on the Model class).
import { Model, Collection } from 'mobx-jsonapi';
class Project extends Model {
type = 'projects'
};
class ProjectsCollection extends Collection {
url() {
return '/jsonapi/projects/';
}
model() {
return Project;
}
}
// Calling fetch() on the collection would make a GET request to '/jsonapi/projects'.
// Calling save() on a model that is new (does not have an id and only exists on the client) will send a POST request to '/jsonapi/projects'.
// Calling save() on a model with an id of '1' would send a PATCH request to '/jsonapi/projects/1'.
Constructor / Initialize
When creating an instance of a collection, you can pass in some initial state as the first argument and an options object as the second argument.
import Companies from './collections/companies';
import Projects from './collections/projects';
const companies = new Companies();
const projects = new Projects({
"meta": {
"totalRecords": 2,
"totalPages": 1
},
"links": {
"self": "http://example.com/projects?page[number]=1",
"first": "http://example.com/projects?page[number]=1",
"next": null,
"prev": null,
"last": null
},
"data": [
{
"id": "1",
"type": "projects",
"attributes": {
"name": "Sample Project 1",
"project_number": "12345",
"start_date": "2017-01-12T03:44:00.433Z",
"end_date": "2017-01-12T03:44:00.433Z",
"status": "active",
"time_zone": "PDT"
},
"relationships": {
"company": {
"data": {
"type": "companies",
"id": "1"
}
}
},
"links": {
"self": "http://example.com/projects/1"
}
},
{
"id": "2",
"type": "projects",
"attributes": {
"name": "Sample Project 2",
"project_number": "12346",
"start_date": "2017-01-12T03:44:00.433Z",
"end_date": "2017-01-12T03:44:00.433Z",
"status": "active",
"time_zone": "PDT"
},
"relationships": {
"company": {
"data": {
"type": "companies",
"id": "1"
}
}
},
"links": {
"self": "http://example.com/projects/2"
}
}
]
}, {
related: {
businesses
}
});
options
Name | Type | Description |
---|---|---|
related | object | Specify references to other model and collection instances. The reverse relationship is also set up automatically. |
models
A collection contains a models property which is a reference an observable array populated with instantiated models for each of the items in the data array provided in the JSON The Collection class also provides a number of methods for working with this array.
length
Returns the length of the collection's models property. Is the same as collection.models.length.
setModels(models, options = {add: true, merge: true, remove: true})
If a model in the list isn't yet in the collection it will be added; if the model is already in the collection its attributes will be merged; and if the collection contains any models that aren't present in the list, they'll be removed.
You can override each of these rules by passing them in the options configuration object.
addModels(models)
Add a single model (or an array of models) to the collection's models and return them. You can either provide an instantiated model(s) or JS object(s).
If you try to add a model to a collection which already exists in the collection, it will be ignored.
Note:Adding a model to a collection does not create or save it to the server. See the create() method in the CRUDsection below for achieving this.
removeModels(models)
Remove a model (or an array of models) from the collection's models, and return them. You can either provide an instantiated model(s) or JS object(s).
Note: Removing a model from a collection does not delete it on the server. See the destroy() method in the CRUD section of the Model docs for achieving this.
getModel(uniqueId)
Retrieves a model with the given id or uuid.
getModelAt(index)
Retrieves a model at the given index.
meta
A collection also contains meta property which is a reference to an observable map populated with any meta information present in the JSON response.
getMeta(key)
Get the current value of an meta key included in the JSON response.
links
A collection also contains links property which is a reference to an observable map populated with anylinkspresent in the JSON response.
getLink(key)
Get the current value of an link key included in the JSON response.
CRUD Actions
All CRUD related methods below return a promise. You can use then and catch to respond to successful and unsuccessful API calls or to combine calls using Promise.all().
Each method has a related observable property that can be used for checking the status of the request. This can be useful for showing UI elements such as a loading icon.
fetch(options = { add: bool, merge: bool, remove: bool, url: string, params: object })
Fetches the latest state from the server via a GET request. Merges the model's attributes and relationships with the response data.
If a model in the list isn't yet in the collection it will be added; if the model is already in the collection its attributes will be merged; and if the collection contains any models that aren't present in the list, they'll be removed.
You can override each of these rules by passing them in the options configuration object.
options
Name | Type | Default | Description |
---|---|---|---|
add | Boolean | true | Add new models to the collection. |
merge | Boolean | true | Update models already in the collection with the new data. |
remove | Boolean | true | Remove any models in the collection that are not present in the new data. |
url | String | undefined | Override the URL for the request. Normally this isn't as fetch makes use of the Models URL by default. |
params | String | undefined | Specify querystring parameters to use in the request. |
Note the 'fetching' observable property can be used to view the status of the request.
class Projects extends Collection {
url() {
return '/jsonapi/projects';
}
};
const projects = new Projects();
// Makes a GET request to '/jsonapi/projects'
projects.fetch().then((collection, response) => {
console.log(projects.fetching) // false
// Do something with the updated collection or response
}).catch((error) => {
console.log(projects.fetching) // false
// Do something with JSON:API error object
});
console.log(projects.fetching) // true
// Perform a fetch but keep any models that are already in the collection and not in the returned server response
projects.fetch({ remove: false });
create(data={},options = { wait: bool })
Creates a new model instance with the passed in data, saves it to the server, then adds it to the collection. The new model is then returned.
By default, the model is added to the collection optimistically, however you can set the `wait` option to true to ensure the model is successfully saved to the server before adding it.
If an id is passed in the data and the model already exists in the collection it will be a no-op.
Note the 'saving' observable property can be used to view the status of the request.
class Projects extends Collection {
url() {
return '/jsonapi/projects';
}
};
const projects = new Projects();
// POST request sent, model added to collection immediately and model instance returned.
const newProject = projects.create({
"type": "projects",
"attributes": {
"name": "A New Project",
"project_number": "123457",
"start_date": "2017-01-12T03:44:00.433Z",
"end_date": "2017-01-12T03:44:00.433Z",
"status": "active",
"time_zone": "PDT"
},
"relationships": {
"company": {
"data": {
"type": "companies",
"id": "1"
}
}
}
});
console.log(projects.length) // 1
// POST request sent, waits for successful response from server before adding the model to the collection
projects.create({
"type": "projects",
"attributes": {
"name": "A New Project",
"project_number": "123457",
"start_date": "2017-01-12T03:44:00.433Z",
"end_date": "2017-01-12T03:44:00.433Z",
"status": "active",
"time_zone": "PDT"
},
"relationships": {
"company": {
"data": {
"type": "companies",
"id": "1"
}
}
}
}, { wait: true }).then((collection, response) => {
console.log(projects.saving) // false
console.log(projects.length) // 2
}).catch((error) => {
console.log(projects.saving) // false
console.log(projects.length) // 1
});
console.log(projects.saving) // true
console.log(projects.length) // 1
Parsing Related Data
One of the nicer features of the JSON:API spec is the ability to reduce requests by including resources related to the primary resource being fetched. See http://jsonapi.org/format/#fetching-includes for the official spec.
setIncluded(included = [])
The setIncluded action can be overriden at the class level. Here you can specify how any included data should be handled. It will be called internally by the collection when fetching new data from the server.
The example below shows how the related company can be fetched for each project.
import { Collection } from 'mobx-jsonapi';
import { action, computed } from 'mobx';
class Companies extends Collection {};
class Projects extends Collection {
// Set included companies data
@action setIncluded(included) {
const companiesData = included.filter((model) => model.type === 'companies');
// Populate the roles collection
if (this.companies && companiesData.length) {
this.companies.set({
data: companiesData
}, { add: true, remove: false, merge: false });
}
}
};
const companies = new Companies();
const projects = new Projects(null, {
related {
companies
}
});
// Fetches the the related company for each project `jsonapi/projects?include[projects]=company`
projects.fetch({
params: {
'include[projects]': 'company'
}
});
// Returned from server
{
"meta": {
"totalRecords": 2,
"totalPages": 1
},
"links": {
"self": "http://example.com/projects?page[number]=1",
"first": "http://example.com/projects?page[number]=1",
"next": null,
"prev": null,
"last": null
},
"data": [
{
"id": "1",
"type": "projects",
"attributes": {
"name": "Sample Project 1",
"projectNumber": "12345",
"startDate": "2017-01-12T03:44:00.433Z",
"endDate": "2017-01-12T03:44:00.433Z",
"status": "active",
"timeZone": "PDT"
},
"relationships": {
"company": {
"data": {
"type": "companies",
"id": "1"
}
}
},
"links": {
"self": "http://example.com/projects/1"
}
},
{
"id": "2",
"type": "projects",
"attributes": {
"name": "Sample Project 2",
"projectNumber": "12346",
"startDate": "2017-01-12T03:44:00.433Z",
"endDate": "2017-01-12T03:44:00.433Z",
"status": "active",
"timeZone": "PDT"
},
"relationships": {
"company": {
"data": {
"type": "companies",
"id": "2"
}
}
},
"links": {
"self": "http://example.com/projects/2"
}
}
],
"included": [
{
"id": "1",
"type": "companies",
"attributes": {
"name": "Acme Inc",
"street_address": "98 Gilles Avenue.",
"city": "Auckland",
"state": "Auckland",
"zip_code": "1023",
"country": "New Zealand",
"phone": "021552497",
"subdomain": "acme",
"primary_color": "#005493"
}
},
{
"id": "2",
"type": "companies",
"attributes": {
"name": "Another Company",
"street_address": "59 Rua Road.",
"city": "Auckland",
"state": "Auckland",
"zip_code": "0602",
"country": "New Zealand",
"phone": "021552497",
"subdomain": "acme",
"primary_color": "#005493"
}
}
]
}
console.log(companies.length) // 2