# Service Plugin
The makeServicePlugin
method creates a vuex plugin which connects a Feathers service to the Vuex store. Once you create a plugin, you must register it in the Vuex store's plugins
section.
# Setup
See the setup documentation to learn the basics of setting up a Service Plugin.
# Configuration
The following options are supported on makeServicePlugin
:
import Model from "../users.model";
const servicePath = 'users'
const servicePlugin = makeServicePlugin({
// necesarry
Model,
service: feathersClient.service(servicePath),
// optional and configurable also by global config
idField: 'id',
tempIdField: '__id',
nameStyle: 'short',
debug: false,
addOnUpsert: false,
autoRemove: false,
preferUpdate: false,
replaceItems: false,
skipRequestIfExists: false,
paramsForServer: ['$populateParams'],
whitelist: [],
enableEvents: true,
handleEvents: {
created: (item, { model, models }) => options.enableEvents,
patched: (item, { model, models }) => options.enableEvents,
updated: (item, { model, models }) => options.enableEvents,
removed: (item, { model, models }) => options.enableEvents
},
// optional and only configurable per service
servicePath: '',
namespace: null,
modelName: 'User',
instanceDefaults: () => ({}),
setupInstance: instance => instance,
state: {},
getters: {},
mutations: {},
actions: {},
//...
});
The following options can also be configured in Global Configuration for every service initialized using feathers-client.js
:
idField {String}
- Default:globalConfig: 'id'
- The field in each record that will contain the idtempIdField {Boolean}
- Default:globalConfig: '__id'
- The field in each temporary record that contains the idnameStyle {'short'|'path'}
- Default:globalConfig: 'short'
- Use the full service path as the Vuex module name, instead of just the last section.debug {Boolean}
- Default:globalConfig: false
- Enable some logging for debuggingaddOnUpsert {Boolean}
- Default:globalConfig: false
- Iftrue
add new records pushed by 'updated/patched' socketio events into store, instead of discarding them.autoRemove {Boolean}
- Default:globalConfig: false
- Iftrue
automatically remove records missing from responses (only use with feathers-rest)preferUpdate {Boolean}
- Default:globalConfig: false
- Iftrue
, callingmodel.save()
will do anupdate
instead of apatch
.replaceItems {Boolean}
- Default:globalConfig: false
- Iftrue
, updates & patches replace the record in the store. Default is false, which merges in changes.skipRequestIfExists {Boolean}
- Default:globalConfig: false
- For get action, iftrue
the record already exists in store, skip the remote request.paramsForServer {Array}
- Default:['$populateParams']
- Custom query operators that are ignored in the find getter, but will pass through to the server. It is preconfigured to work with the$populateParams
custom operator from feathers-graph-populate (opens new window).whitelist {Array}
- Custom query operators that will be allowed in the find getter.enableEvents {Boolean}
- Default:globalConfig: true
- Iffalse
socket event listeners will be turned offhandleEvents {Object}
: For this to workenableEvents
must betrue
created {Function}
- Default:(item, { model, models }) => options.enableEvents
- handlecreated
events, return true to add to the storepatched {Function}
- Default:(item, { model, models }) => options.enableEvents
- handlecreated
events, return true to update in the storeupdated {Function}
- Default:(item, { model, models }) => options.enableEvents
- handlecreated
events, return true to update in the storeremoved {Function}
- Default:(item, { model, models }) => options.enableEvents
- handleremoved
events, return true to remove from the store
The following options can only configured individually per service plugin
servicePath {String}
- Not all Feathers service plugins expose the service path, so it can be manually specified when missing.namespace {String}
, - Default:nameStyle === 'short' ? ${afterLastSlashOfServicePath} : ${stripSlashesFromServicePath}
- Customize the Vuex module name. Overrides nameStyle.modelName {String}
- Default:${ServicePlugin.Model.modelName}
instanceDefaults {Function}
- Default:() => ({})
- Override this method to provide default data for new instances. If using Model classes, specify this as a static class property.setupInstance {Function}
- Default:instance => instance
- Override this method to setup data types or related data on an instance. If using Model classes, specify this as a static class property.state {Object}
- Default::null
- Pass customstates
to the service plugin or modify existing onesgetters {Object}
- Default:null
- Pass customgetters
to the service plugin or modify existing onesmutations {Object}
- Default:null
- Pass custommutations
to the service plugin or modify existing onesactions {Object}
- Default:null
- Pass customactions
to the service plugin or modify existing ones
# Realtime by Default
Service plugins automatically listen to all socket messages received by the Feathers Client. This can be disabled by setting enableEvents: false
in the options, as shown above.
# The FeathersClient Service
Once the service plugin has been registered with Vuex, the FeathersClient Service will have a new service.FeathersVuexModel
property. This provides access to the service's Model class.
import { models } from 'feathers-vuex'
feathersClient.service('todos').FeathersVuexModel === models.api.Todo // true
# Service State
Each service comes loaded with the following default state:
{
ids: [],
idField: 'id',
keyedById: {},
tempsById: {},
tempsByNewId: {},
pagination: {
defaultLimit: null,
defaultSkip: null
},
servicePath: 'v1/todos'
modelName: 'Todo',
autoRemove: false,
replaceItems: false,
pagination: {
ids: []
limit: 0
skip: 0
ip: 0
total: 0,
mostRecent: any
},
paramsForServer: ['$populateParams'],
whitelist: [],
isFindPending: false,
isGetPending: false,
isCreatePending: false,
isUpdatePending: false,
isPatchPending: false,
isRemovePending: false,
errorOnfind: undefined,
errorOnGet: undefined,
errorOnCreate: undefined,
errorOnUpdate: undefined,
errorOnPatch: undefined,
errorOnRemove: undefined,
isIdCreatePending: [],
isIdUpdatePending: [],
isIdPatchPending: [],
isIdRemovePending: [],
}
ids {Array}
- an array of plain ids representing the ids that belong to each object in thekeyedById
map.idField {String}
- the name of the field that holds each item's id. Default:'id'
keyedById {Object}
- a hash map keyed by the id of each item.tempsById {Object}
- a hash map of temporary records, keyed by tempIdField of each itemtempsByNewId {Object}
- temporary storage for temps while getting transferred from tempsById to keyedByIdservicePath {String}
- the full service path, even if you alias the namespace to something else.modelName {String}
- the key in the $FeathersVuex plugin where the model will be found.autoRemove {Boolean}
- indicates that this service will not automatically remove results missing from subsequent requests. Only use with feathers-rest. Default is false.replaceItems {Boolean}
- When set to true, updates and patches will replace the record in the store instead of merging changes. Default is falsepagination {Object}
- provides informaiton about the last made queriesparamsForServer {Array}
- Custom query operators that are ignored in the find getter, but will pass through to the server.whitelist {Array}
- Custom query operators that will be allowed in the find getter.
The following state attributes allow you to bind to the pending state of requests:
isFindPending {Boolean}
-true
if there's a pendingfind
request.false
if not.isGetPending {Boolean}
-true
if there's a pendingget
request.false
if not.isCreatePending {Boolean}
-true
if there's a pendingcreate
request.false
if not.isUpdatePending {Boolean}
-true
if there's a pendingupdate
request.false
if not.isPatchPending {Boolean}
-true
if there's a pendingpatch
request.false
if not.isRemovePending {Boolean}
-true
if there's a pendingremove
request.false
if not.
The following state attribute will be populated with any request error, serialized as a plain object:
errorOnFind {Error}
errorOnGet {Error}
errorOnCreate {Error}
errorOnUpdate {Error}
errorOnPatch {Error}
errorOnRemo {Error}
The following state attributes allow you to bind to the pending state of requests per item ID
isIdCreatePending {Array}
- Containsid
if there's a pendingcreate
request forid
.isIdUpdatePending {Array}
-Containsid
if there's a pendingupdate
request forid
.isIdPatchPending {Array}
- Containsid
if there's a pendingpatch
request forid
.isIdRemovePending {Array}
- Containsid
if there's a pendingremove
request forid
.
# Service Getters
Service modules include the following getters:
list {Array}
- an array of items. The array form ofkeyedById
Read only.find(params) {Function}
- a helper function that allows you to use the Feathers Adapter Common API (opens new window) and Query API (opens new window) to pull data from the store. This allows you to treat the store just like a local Feathers database adapter (but without hooks).params {Object}
- an object with aquery
object and optional properties. You can set the following properties:params.query {Boolean}
- Thequery
is in the FeathersJS query format.params.temps {Boolean}
- Default:false
- iftrue
also consider temporary records fromtempsById
params.copies {Boolean}
- Default:false
- iftrue
: first search for the regular records and then replace the records with the related copies fromcopiesById
count(params) {Function}
- a helper function that counts items in the store matching the provided query in the params and returns this number 3.12.0+params {Object}
- an object with aquery
object and an optionaltemps
boolean property.
get(id[, params]) {Function}
- a function that allows you to query the store for a single item, by id. It works the same way asget
requests in Feathers database adapters.id {Number|String}
- the id of the data to be retrieved by id from the store.params {Object}
- an object containing a Feathersquery
object.
The following getters ease access to per-instance pending status
isCreatePendingById(id) {Function}
- Check ifcreate
is pending forid
isUpdatePendingById(id) {Function}
- Check ifupdate
is pending forid
isPatchPendingById(id) {Function}
- Check ifpatch
is pending forid
isRemovePendingById(id) {Function}
- Check ifremove
is pending forid
isSavePendingById(id) {Function}
- Check ifcreate
,update
, orpatch
is pending forid
isPendingById(id) {Function}
- Check ifcreate
,update
,patch
orremove
is pending forid
# Service Mutations
The following mutations are included in each service module.
Note: you would typically not call these directly, but instead with
store.commit('removeItem', 'itemId')
. Using vuex's mapMutations on a Vue component can simplify that tothis.removeItem('itemId')
# addItem(state, item)
Adds a single item to the keyedById
map.
item {Object}
- The item to be added to the store.
# addItems(state, items)
Adds an array of items to the keyedById
map.
items {Array}
- the items to be added to the store.
# updateItem(state, item)
Updates an item in the store to match the passed in item
.
item {Object}
the item, includingid
, to replace the currently-stored item.
# updateItems(state, items)
Updates multiple items in the store to match the passed in array of items.
items {Array}
- An array of items.
# removeItem(state, item)
Removes a single item. item
can be
item {Number|String|Object}
- The item or id of the item to be deleted.
# removeTemps(state, tempIds)
// Removes temp records. Also cleans up tempsByNewId
items {Array}
- An array of ids or of objects with tempIds that will be removed from the data store
# removeItems(state, items)
Removes the passed in items or ids from the store.
items {Array}
- An array of ids or of objects with ids that will be removed from the data store.
# clearAll(state)
Clears all data from ids
, keyedById
, and currentId
# Mutations for Managing Pending State
The following mutations are called automatically by the service actions, and will rarely, if ever, need to be used manually.
setPending(state, method)
- sets theis${method}Pending
attribute to truesetIdPending(state, { method, id })
- addsid
toisId${method}Pending
arrayunsetPending(state, method)
- sets theis${method}Pending
attribute to falseunsetIdPending(state, { method, id })
- removesid
fromisId${method}Pending
array
# Mutations for Managing Errors
The following mutations are called automatically by the service actions, and will rarely need to be used manually.
setError(state, { method, error })
- sets theerrorOn${method}
attribute to the errorclearError(state, method)
- sets theerrorOn${method}
attribute tonull
# Service Actions
An action is included for each of the Feathers service interface methods. These actions will affect changes in both the Feathers API server and the Vuex store.
All of the Feathers Service Methods (opens new window) are supported. Because Vuex only supports providing a single argument to actions, there is a slight change in syntax that works well. If you need to pass multiple arguments to a service method, pass an array to the action with the order of the array elements matching the order of the arguments. See each method for examples.
Note: If you use the Feathers service methods, directly, the store will not change. Only the actions will cause store changes.
# find(params)
Query an array of records from the server & add to the Vuex store.
params {Object}
- An object containing aquery
object and an optionalpaginate
boolean. You can setparams.paginate
tofalse
to disable pagination for a single request.
let params = {query: {completed: true}}
store.dispatch('todos/find', params)
See the section about pagination, below, for more information that is applicable to the find
action. Make sure your returned records have a unique field that matches the idField
option for the service plugin.
# afterFind(response)
The afterFind
action is called by the find
action after a successful response is added to the store. It is called with the current response. By default, it is a no-op (it literally does nothing), and is just a placeholder for you to use when necessary. See the sections on customizing the default store and Handling custom server responses for example usage.
# count(params)
3.12.0+
Count items on the server matching the provided query.
params {Object}
- An object containing aquery
object. In the background$limit: 0
will be added to thequery
to perform a (fast) counting query against the database.
Note: it only works for services with enabled pagination!
let params = {query: {completed: false}}
store.dispatch('todos/count', params)
This will run a (fast) counting query against the database and return a page object with the total and an empty data array.
# get(id)
or get([id, params])
Query a single record from the server & add to Vuex store
id {Number|String}
- theid
of the record being requested from the API server.params {Object}
- An object containing aquery
object.
store.dispatch('todos/get', 1)
// Use an array to pass params
let params = {}
store.dispatch('todos/get', [1, params])
Make sure your returned records have a unique field that matches the idField
option for the service plugin.
# create(data|ParamArray)
Create one or multiple records. Note that the method is overloaded to accept two types of arguments. If you want a consistent interface for creating single or multiple records, use the array syntax, described below. Creating multiple records requires using the paramArray
syntax.
data {Object|ParamArray}
- if an object is provided, a single record will be created.
let newTodo = {description: 'write good tests'}
store.dispatch('todos/create', newTodo)
data {ParamArray}
- if an array is provided, it is assumed to have this structure:ParamArray {Array}
- array containing the two parameters that Feathers'service.create
method accepts.data {Object|Array}
- the data to create. Providing an object creates a single record. Providing an array of objects creates multiple records.params {Object}
- optional - an object containing aquery
object. Can be useful in rare situations.
Make sure your returned records have a unique field that matches the idField
option for the service plugin.
# update(paramArray)
Update (overwrite) a record.
paramArray {Array}
- array containing the three parameters update accepts.id {Number|String}
- theid
of the existing record being requested from the API server.data {Object}
- the data that will overwrite the existing recordparams {Object}
- An object containing aquery
object.
let data = {id: 5, description: 'write your tests', completed: true}
let params = {}
// Overwrite item 1 with the above data (FYI: Most databases won't let you change the id.)
store.dispatch('todos/update', [1, data, params])
Alternatively in a Vue component
import { mapActions } from 'vuex'
export default {
methods: {
...mapActions('todos', [ 'update' ]),
addTodo () {
let data = {id: 5, description: 'write your tests', completed: true}
this.update([1, data, {}])
}
}
}
Make sure your returned records have a unique field that matches the idField
option for the service plugin.
# patch(paramArray)
Patch (merge in changes) one or more records
paramArray {Array}
- array containing the three parameters patch takes.id {Number|String}
- theid
of the existing record being requested from the API server.data {Object}
- the data that will be merged into the existing recordparams {Object}
- An object containing aquery
object. If params.data is provided, it will be used as the patch data, providing a simple way to patch with partial data.
let data = {description: 'write your tests', completed: true}
let params = {}
store.dispatch('todos/patch', [1, data, params])
Make sure your returned records have a unique field that matches the idField
option for the service plugin.
# remove(id)
Remove/delete the record with the given id
.
id {Number|String}
- theid
of the existing record being requested from the API server.
store.dispatch('todos/remove', 1)
Make sure your returned records have a unique field that matches the idField
option for the service plugin.
# Service Events
By default, the service plugin listens to all of the FeathersJS events:
created
events will add new record to the store.patched
events will add (if new) or update (if present) the record in the store.updated
events will add (if new) or update (if present) the record in the store.removed
events will remove the record from the store, if present.
This behavior can be turned off completely by passing enableEvents: false
in either the global Feathers-Vuex options or in the service plugin options. If you configure this at the global level, the service plugin level will override it. For example, if you turn off events at the global level, you can enable them for a specific service by setting enableEvents: true
on that service's options.
# Custom Event Handlers 3.1.0+
As of version 3.1, you can customize the behavior of the event handlers, or even perform side effects based on the event data. This is handled through the new handleEvents
option on the service plugin. Here is an example of how you might use this:
handleEvents: {
created: (item, { model, models }) => {
// Perform a side effect to remove any record with the same `name`
const existing = Model.findInStore({ query: { name: item.name }}).data[0]
if (existing) {
existing.remove()
}
// Perform side effects with other models.
const { SomeModel } = models.api
new SomeModel({ /* some custom data */ }).save()
// Access the store through model.store
const modelState = model.store.state[model.namespace]
if (modelState.keyedById[5]) {
console.log('we accessed the vuex store')
}
// If true, the new item will be stored.
return true
},
updated: () => false, // Ignore `updated` events.
patched: item => item.hasPatchedAttribute && item.isWorthKeeping,
removed: item => true // The default value, will remove the record from the store
}
As shown above, each handler has two possible uses:
- Control the default behavior of the event by returning a boolean.
- For
created
,patched
, andupdated
a truthy return will add or update the item in the store. - For
removed
a truthy return will remove the item from the store, if present.
- Perform side effects using the current service
model
or with othermodels
. Themodels
object is the same as the$FeathersVuex
object in the Vue plugin.
Each handler receives the following arguments:
item
: the record sent from the API serverutils
: an object containing the following propertiesmodel
The current service's Model class.models
The same as the$FeathersVuex
object, gives you access to each api with their respective model classes.
You do not have to specify a handler for every event. Any that are not specified in your service-specific handleEvents
, will fall back to using the handleEvents
handler in your global options. If none are defined for the service or globally, the default behavior is controlled by the enableEvents
option.
# Handling complex events 3.10.0+
If your application emits the standard Feathers service events inside a nested object with additional data, you can use handleEvents
to tell FeathersVuex what part of that data is actually the model data that should be used to update the store.
To do this, use handleEvents
as described before, but return a tuple [affectsStore, modelData]
from your handler.
affectsStore
a truthy value indicates the event should update the storemodelData
is the model data used to update the store
For example, you've configured your Feathers API to emit patched
events for your Todos
service that include context about the event which look like
{
"$context": {
"time": 1445411009000,
"userId": 121,
"deviceId": "Marty's iPhone"
},
"event": {
"id": 88,
"text": "Get back to the past",
"done": true
}
}
For this service to play nicely with FeathersVuex, you'll need to use handleEvents
handleEvents: {
patched: (item, { model, models }) => {
// Perform any side effects...
// If the first element is truthy, the item will update the store
// The second element is the actual model data to add to the store
return [true, item.event]
}
}
The original event data is bubbled to Model events so listeners receive the full event context.
# Pagination and the find
action
Both the find
action and the find
getter support pagination. There are differences in how they work.
Important: For the built in pagination features to work, you must not directly manipulate the context.params
object in any before hooks. You can still use before hooks as long as you clone the params object, then make changes to the clone.
# The find
action
The find
action queries data from the remote server. It returns a promise that resolves to the response from the server. The presence of pagination data will be determined by the server.
feathers-vuex@1.0.0
can store pagination data on a per-query basis. The pagination
store attribute maps queries to their most-recent pagination data. The default pagination state looks like this:
{
pagination: {
defaultLimit: null,
defaultSkip: null
}
}
You should never manually change these values. They are managed internally.
There's not a lot going on, by default. The defaultLimit
and defaultSkip
properties are null until a query is made on the service without $limit
or $skip
. In other words, they remain null
until an empty query comes through, like the this one:
params = { query: {} }
{
pagination : {
defaultLimit: 25,
defaultSkip: 0,
default: {
mostRecent: {
query: {},
queryId: '{}',
queryParams: {},
pageId: '{$limit:25,$skip:0}',
pageParams: { $limit: 25, $skip: 0 },
queriedAt: 1538594642481
},
'{}': {
total: 155,
queryParams: {},
'{$limit:25,$skip:0}': {
pageParams: { $limit: 25, $skip: 0 },
ids: [ 1, 2, 3, 4, '...etc', 25 ],
queriedAt: 1538594642481
}
}
}
}
}
It looks like a lot just happened, so let's walk through it. First, notice that we have values for defaultLimit
and defaultSkip
. These come in handy for the find
getter, which will be covered later.
# The qid
The state now also contains a property called default
. This is the default qid
, which is a "query identifier" that you choose. Unless you're building a small demo, your app will require to storing pagination information for more than one query. For example, two components could make two distinct queries against this service. You can use the params.qid
(query identifier) property to assignn identifier to the query. If you set a qid
of mainListView
, for example, the pagination for this query will show up under pagination.mainListView
. The pagination.default
property will be used any time a params.qid
is not provided. Here's an example of what this might look like:
params = { query: {}, qid: 'mainListView' }
// Data in the store
{
pagination : {
defaultLimit: 25,
defaultSkip: 0,
mainListView: {
mostRecent: {
query: {},
queryId: '{}',
queryParams: {},
pageId: '{$limit:25,$skip:0}',
pageParams: { $limit: 25, $skip: 0 },
queriedAt: 1538594642481
},
'{}': {
total: 155,
queryParams: {},
'{$limit:25,$skip:0}': {
pageParams: { $limit: 25, $skip: 0 },
ids: [ 1, 2, 3, 4, '...etc', 25 ],
queriedAt: 1538594642481
}
}
}
}
}
The above example is almost exactly the same as the previous one. The only difference is that the default
key is now called mainListView
. This is because we provided that value as the qid
in the params. Let's move on to the properties under the qid
.
# The mostRecent
object
The mostRecent
propery contains information about the most recent query. These properties provide insight into how pagination works. The two most important properties are the queryId
and the pageId
.
- The
queryId
describes the set of data we're querying. It's a stable, stringified version of all of the query params except for$limit
and$skip
. - The
pageId
holds information about the current "page" (as in "page-ination"). A page is described using$limit
and$skip
.
The queryParams
and pageParams
are the non-stringified queryId
and pageId
. The query
attribute is the original query that was provided in the request params. Finally, the queriedAt
is a timestamp of when the query was performed.
# The queryId
and pageId
tree
The rest of the qid
object is keyed by queryId
strings. Currently, we only have a single queryId
of '{}'
. In the queryId
object we have the total
numer of records (as reported by the server) and the pageId
of '{$limit:25,$skip:0}'
'{}': { // queryId
total: 155,
queryParams: {},
'{$limit:25,$skip:0}': { // pageId
pageParams: { $limit: 25, $skip: 0 },
ids: [ 1, 2, 3, 4, '...etc', 25 ],
queriedAt: 1538594642481
}
}
The pageId
object contains the queriedAt
timestamp of when we last queried this page of data. It also contains an array of ids
, holding only the ids
of the records returned from the server.
# Additional Queries and Pages
As more queries are made, the pagination data will grow to represent what we have in the store. In the following example, we've made an additional query for sorted data in the mainListView
qid
. We haven't filtered the list down any, so the total
is the same as before. We have sorted the data by the isComplete
attribute, which changes the queryId
. You can see the second queryId
object added to the mainListView
qid
:
params = { query: {}, qid: 'mainListView' }
params = { query: { $limit: 10, $sort: { isCompleted: 1 } }, qid: 'mainListView' }
// Data in the store
{
pagination : {
defaultLimit: 25,
defaultSkip: 0,
mainListView: {
mostRecent: {
query: { $sort: { isCompleted: 1 } },
queryId: '{$sort:{isCompleted:1}}',
queryParams: { $sort: { isCompleted: 1 } },
pageId: '{$limit:10,$skip:0}',
pageParams: { $limit: 10, $skip: 0 },
queriedAt: 1538595856481
},
'{}': {
total: 155,
queryParams: {},
'{$limit:25,$skip:0}': {
pageParams: { $limit: 25, $skip: 0 },
ids: [ 1, 2, 3, 4, '...etc', 25 ],
queriedAt: 1538594642481
}
},
'{$sort:{isCompleted:1}}': {
total: 155,
queryParams: {},
'{$limit:10,$skip:0}': {
pageParams: { $limit: 10, $skip: 0 },
ids: [ 4, 21, 19, 29, 1, 95, 62, 21, 67, 125 ],
queriedAt: 1538594642481
}
}
}
}
}
In summary, any time a query param other than $limit
and $skip
changes, we get a new queryId
. Whenever $limit
and $skip
change, we get a new pageId
inside the current queryId
.
# Why use this pagination structure
Now that we've reviewed how pagination tracking works under the hood, you might be asking "Why?" There are a few reasons:
- Improve performance with cacheing. It's now possible to skip making a query if we already have valid data for the current query. The
makeFindMixin
mixin makes this very easy with its built-inqueryWhen
feature. - Allow fall-through cacheing of paginated data. A common challenge occurs when you provide the same query params to the
find
action and thefind
getter. As you'll learn in the next section, thefind
getter allows you to make queries against the Vuex store as though it were a Feathers database adapter. But what happens when you pass{ $limit: 10, $skip: 10 }
to the action and getter?
First, lets review what happens with thefind
action. The database is aware of all 155 records, so it skips the first 10 and returns the next 10 records. Those records get populated in the store, so the store now has 10 records. Now we pass the query to thefind
getter and tell it to$skip: 10
. It skips the only 10 records that are in the store and returns an empty array! That's definitely not what we wanted.
Since we're now storing this pagination structure, we can build a utility around thefind
getter which will allow us to return the same data with the same query. The data is still reactive and will automatically update when a record changes.
There's one limitation to this solution. What happens when you add a new record that matches the current query? Depending on where the new record would be sorted into the current query, part or all of the cache is no longer valid. It will stay this way until a new query is made. To get live (reactive) lists, you have to use the find
getter with its own distinct query, removing the $limit
and $skip
values. This way, when a new record is created, it will automatically get added to the array in the proper place.
# Pagination and the find
getter
The find
getter queries data from the local store using the same Feathers query syntax as on the server. It is synchronous and returns the results of the query with pagination. Pagination cannot be disabled. It accepts a params object with a query
attribute. It does not use any other special attributes. The returned object looks just like a paginated result that you would receive from the server:
params = { query: {} }
// The returned results object
{
data: [{ _id: 1, ...etc }, ...etc],
limit: 0,
skip: 0,
total: 3
}
# Customizing a Service's Default Store
# New extend
option for makeServicePlugin
3.14.0+
As of version 3.14.0
, the makeServicePlugin
now supports an extend
method that allows customizing the store and gives access to the actual Vuex store
object, as shown in this example:
import { makeServicePlugin } from ‘feathers-vuex’
import { feathersClient } from ‘./feathers-client.js’
class Todo { /* truncated */ }
export default makeServicePlugin({
Model: Todo,
service: feathersClient.service(‘todos’),
extend({ store, module }) {
// Listen to other parts of the store
store.watch(/* truncated */)
return {
state: {},
getters: {},
mutations: {},
actions: {}
}
}
})
# Deprecated options for customizing the store
Before version 3.14.0
, you can customize the store using the options for state
, getters
, mutations
, and actions
, as shown below. This method is now deprecated and will be removed from Feathers-Vuex 4.0.
// src/store/services/users.js
import feathersClient, { makeServicePlugin, BaseModel } from '../../feathers-client'
class Asset extends BaseModel {
constructor(data, options) {
super(data, options)
}
// Required for $FeathersVuex plugin to work after production transpile.
static modelName = 'Asset'
// Define default properties here
static instanceDefaults() {
return {
email: '',
password: ''
}
}
}
const servicePath = 'assets'
const servicePlugin = makeServicePlugin({
Model: Asset,
service: feathersClient.service(servicePath),
servicePath,
state: {
test: true
},
getters: {
getSomeData () {
return 'some data'
}
},
mutations: {
setTest (state, val) {
state.test = val;
},
},
actions: {
// Overwriting the built-in `afterFind` action.
afterFind ({ commit, dispatch, getters, state }, response) {
// Do something with the response.
// Keep in mind that the data is already in the store.
},
asyncStuff ({ state, getters, commit, dispatch }, args) {
commit('setTestToTrue')
return new Promise.resolve("")
}
}
})
export default servicePlugin