Reactive API extensions for Feathers services
Reactive API extensions for Feathers
feathers-reactiveadds a
watch()method to services. The returned object implements all service methods as RxJS v6 observables that automatically update on real-time events.
The following options are supported:
idField(mandatory): The id property field of your services. Depends on your service/database. Usually 'id' (SQL, Rethinkdb, …) or '_id' (MongoDB, NeDB, … ).
dataField(default:
data): The data property field in paginated responses
listStrategy(default:
smart): The strategy to use for streaming the data. Can be
smart,
alwaysor
never. Avoid using
alwayswhenever possible.
sorter(
function(query, options) {}): A function that returns a sorting function for the given query and option including pagination and limiting. Does not need to be customized unless there is a sorting mechanism other than Feathers standard in place.
matcher(
function(query)): A function that returns a function which returns whether an item matches the original query or not.
pipe(
operator | operator[]) One or multiple rxjs operators of the form
function(observable) => observablelike you would pass them to an Observable's .pipe method. The supplied operators are applied to any Observable created by
feathers-reactive.
options.pipe: tap(data => console.log(data))would log every emitted value to the console.
const feathers = require('feathers'); const reactive = require('feathers-reactive');const app = feathers().configure(reactive(options));
With
feathers-reactiveconfigured on the application individual options can be set at the service level with
service.rx:
// Set a different id field app.service('todos').rx({ idField: '_id' });
Each method call can also pass its own options via
params.rx:
// Never update data for this method call app.service('todos').watch({ listStrategy: 'never' }).find();
List strategies are used to determine how a data stream behaves. Currently there are three strategies:
never- Returns a stream from the service promise that only emits the method call data and never updates after that
smart(default) - Returns a stream that smartly emits updated list data based on the services real-time events. It does not re-query any new data (but does not cover some cases in which the
alwaysstrategy can be used).
always- Re-runs the original query to always get fresh data from the server on any matching real-time event. Avoid this list strategy if possible since it will put a higher load on the server than necessary.
const feathers = require('@feathersjs/feathers'); const memory = require('feathers-memory'); const rx = require('feathers-reactive');const app = feathers() .configure(rx({ idField: 'id' })) .use('/messages', memory());
const messages = app.service('messages');
messages.create({ text: 'A test message' }).then(() => { // Get a specific message with id 0. Emit the message data once it resolves // and every time it changes e.g. through an updated or patched event messages.watch().get(0).subscribe(message => console.log('My message', message));
// Find all messages and emit a new list every time anything changes messages.watch().find().subscribe(messages => console.log('Message list', messages));
setTimeout(() => { messages.create({ text: 'Another message' }).then(() => setTimeout(() => messages.patch(0, { text: 'Updated message' }), 1000) ); }, 1000); });
Will output:
My message { text: 'A test message', id: 0 } Message list [ { text: 'A test message', id: 0 } ] Message list [ { text: 'A test message', id: 0 }, { text: 'Another message', id: 1 } ] My message { text: 'Updated message', id: 0 } Message list [ { text: 'Updated message', id: 0 }, { text: 'Another message', id: 1 } ]
Let's assume a simple Feathers Socket.io server in
app.jslike this:
npm install @feathersjs/feathers @feathersjs/socketio feathers-memory
const feathers = require('@feathersjs/feathers'); const socketio = require('@feathersjs/socketio'); const memory = require('feathers-memory');const app = feathers() .configure(socketio()) .use('/todos', memory());
app.on('connection', connection => app.channel('everybody').join(connection)); app.publish(() => app.channel('everybody'));
app.listen(3030).on('listening', () => console.log('Feathers Socket.io server running on localhost:3030') );
For an ES5 compatible version on the client (e.g. when using
create-react-app) you can import
feathers-reactive/dist/feathers-reactive. In
client.js:
import io from 'socket.io-client'; import feathers from '@feathersjs/client'; import rx from 'feathers-reactive/dist/feathers-reactive';const socket = io('http://localhost:3030'); const app = feathers() .configure(feathers.socketio(socket)) .configure(rx({ idField: 'id' }));
export default app;
A real-time ReactJS Todo application (with Bootstrap styles) can look like this (see the examples/react-todos folder for a working example);
import React, { Component } from 'react'; import client from './client';class App extends Component { constructor (props) { super(props); this.state = { todos: [], text: '' }; }
componentDidMount () { this.todos = client.service('todos').watch() .find().subscribe(todos => this.setState(todos)); }
componentWillUnmount () { this.todos.unsubscribe(); }
updateText (ev) { this.setState({ text: ev.target.value }); }
createTodo (ev) { client.service('todos').create({ text: this.state.text, complete: false }); this.setState({ text: '' }); ev.preventDefault(); }
updateTodo (todo, ev) { todo.complete = ev.target.checked; client.service('todos').patch(todo.id, todo); }
deleteTodo (todo) { client.service('todos').remove(todo.id); }
render () { const renderTodo = todo =>
return <div classname="container" id="todos">
<h1>Feathers real-time Todos</h1>
<ul classname="todos list-unstyled">{this.state.todos.map(renderTodo)}</ul>
<form role="form" classname="create-todo" onsubmit="{this.createTodo.bind(this)}">
<div classname="form-group">
<input type="text" classname="form-control" name="description" placeholder="Add a new Todo" onchange="{this.updateText.bind(this)}" value="{this.state.text}">
</div>
<button type="submit" classname="btn btn-info col-md-12">
Add Todo
</button>
</form>
</div>;
} }
export default App;
Copyright (c) 2018
Licensed under the MIT license.