vue-rx
Version:
RxJS bindings for Vue
312 lines (231 loc) • 8.34 kB
Markdown
# vue-rx [](https://circleci.com/gh/vuejs/vue-rx/tree/master)
English | [简体中文](README-CN.md)
[RxJS v6](https://github.com/ReactiveX/rxjs) integration for Vue.js.
> **BREAKING CHANGES from 5.0**
> - vue-rx v6 now only works with RxJS v6 by default. If you want to keep using RxJS v5 style code, install `rxjs-compat`.
### Installation
#### NPM + ES2015
**`rxjs` is required as a peer dependency.**
``` bash
npm install vue vue-rx rxjs --save
```
``` js
import Vue from 'vue'
import VueRx from 'vue-rx'
Vue.use(VueRx)
```
When bundling via webpack, `dist/vue-rx.esm.js` is used by default. It imports the minimal amount of Rx operators and ensures small bundle sizes.
#### Global Script
To use in a browser environment, use the UMD build `dist/vue-rx.js`. When in a browser environment, the UMD build assumes `window.rxjs` to be already present, so make sure to include `vue-rx.js` after Vue.js and RxJS. It also installs itself automatically if `window.Vue` is present.
Example:
``` html
<script src="https://unpkg.com/rxjs/bundles/rxjs.umd.js"></script>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="../dist/vue-rx.js"></script>
```
### Usage
``` js
// provide Rx observables with the `subscriptions` option
new Vue({
el: '#app',
subscriptions: {
msg: messageObservable
}
})
```
``` html
<!-- bind to it normally in templates -->
<div>{{ msg }}</div>
```
The `subscriptions` options can also take a function so that you can return unique observables for each component instance:
``` js
import { Observable } from 'rxjs'
Vue.component('foo', {
subscriptions: function () {
return {
msg: new Observable(...)
}
}
})
```
The observables are exposed as `vm.$observables`:
``` js
const vm = new Vue({
subscriptions: {
msg: messageObservable
}
})
vm.$observables.msg.subscribe(msg => console.log(msg))
```
### `v-stream`: Streaming DOM Events
`vue-rx` provides the `v-stream` directive which allows you to stream DOM events to an Rx Subject. The syntax is similar to `v-on` where the directive argument is the event name, and the binding value is the target Rx Subject.
``` html
<button v-stream:click="plus$">+</button>
```
Note that you need to declare `plus$` as an instance of `rxjs.Subject` on the vm instance before the render happens, just like you need to declare data. You can do that right in the `subscriptions` function:
``` js
import { Subject } from 'rxjs'
import { map, startWith, scan } from 'rxjs/operators'
new Vue({
subscriptions () {
// declare the receiving Subjects
this.plus$ = new Subject()
// ...then create subscriptions using the Subjects as source stream.
// the source stream emits in the format of `{ event: HTMLEvent, data?: any }`
return {
count: this.plus$.pipe(
map(() => 1),
startWith(0),
scan((total, change) => total + change)
)
}
}
})
```
Or, use the `domStreams` convenience option:
``` js
new Vue({
// requires `Rx` passed to Vue.use() to expose `Subject`
domStreams: ['plus$'],
subscriptions () {
// use this.plus$
}
})
```
Finally, you can pass additional data to the stream using the alternative syntax:
``` html
<button v-stream:click="{ subject: plus$, data: someData }">+</button>
```
This is useful when you need to pass along temporary variables like `v-for` iterators. You can get the data by simply plucking it from the source stream:
``` js
const plusData$ = this.plus$.pipe(pluck('data'))
```
Starting in 3.1 you can also pass along extra options (passed along to native `addEventListener` as the 3rd argument):
``` html
<button v-stream:click="{
subject: plus$,
data: someData,
options: { once: true, passive: true, capture: true }
}">+</button>
```
See [example](https://github.com/vuejs/vue-rx/blob/master/example/counter.html) for actual usage.
### `v-stream`: Streaming Custom Events from Child Components
Similar to streaming `DOM` events, `v-stream` can be used on components as well and will create observables from custom events emitted by the child component. It works similar to `v-on`:
```html
<div>
<!-- Custom component -->
<pagination v-on:change="pageChanged()"></pagination>
<!-- v-stream with custom component -->
<pagination v-stream:change="pageChange$"></pagination>
</div>
```
### Other API Methods
#### `$watchAsObservable(expOrFn, [options])`
This is a prototype method added to instances. You can use it to create an observable from a value watcher. The emitted value is in the format of `{ newValue, oldValue }`:
``` js
import { pluck, map } from 'rxjs/operators'
const vm = new Vue({
data: {
a: 1
},
subscriptions () {
// declaratively map to another property with Rx operators
return {
aPlusOne: this.$watchAsObservable('a').pipe(
pluck('newValue'),
map(a => a + 1)
)
}
}
})
// or produce side effects...
vm.$watchAsObservable('a')
.subscribe(
({ newValue, oldValue }) => console.log('stream value', newValue, oldValue),
err => console.error(err),
() => console.log('complete')
)
```
The optional `options` object accepts the same options as `vm.$watch`.
#### `$eventToObservable(event)`
Convert vue.$on (including lifecycle events) to Observables. The emitted value is in the format of `{ name, msg }`:
``` js
import { interval } from 'rxjs'
import { take, takeUntil } from 'rxjs/operators'
const vm = new Vue({
created () {
this.$eventToObservable('customEvent')
.subscribe((event) => console.log(event.name,event.msg))
}
})
// vm.$once vue-rx version
this.$eventToObservable('customEvent').pipe(
take(1)
)
// Another way to auto unsub:
let beforeDestroy$ = this.$eventToObservable('hook:beforeDestroy').pipe(take(1))
interval(500)
.pipe(takeUntil(beforeDestroy$))
```
#### `$subscribeTo(observable, next, error, complete)`
This is a prototype method added to instances. You can use it to subscribe to an observable, but let VueRx manage the dispose/unsubscribe.
``` js
import { interval } from 'rxjs'
const vm = new Vue({
mounted () {
this.$subscribeTo(interval(1000), function (count) {
console.log(count)
})
}
})
```
#### `$fromDOMEvent(selector, event)`
This is a prototype method added to instances. Use it to create an observable from DOM events within the instances' element. This is similar to `Rx.Observable.fromEvent`, but usable inside the `subscriptions` function even before the DOM is actually rendered.
`selector` is for finding descendant nodes under the component root element, if you want to listen to events from root element itself, pass `null` as first argument.
``` js
import { pluck } from 'rxjs/operators'
const vm = new Vue({
subscriptions () {
return {
inputValue: this.$fromDOMEvent('input', 'keyup').pipe(
pluck('target', 'value')
)
}
}
})
```
#### `$createObservableMethod(methodName)`
Convert function calls to observable sequence which emits the call arguments.
This is a prototype method added to instances. Use it to create a shared hot observable from a function name. The function will be assigned as a vm method.
```html
<custom-form :onSubmit="submitHandler"></custom-form>
```
``` js
const vm = new Vue({
subscriptions () {
return {
// requires `share` operator
formData: this.$createObservableMethod('submitHandler')
}
}
})
```
You can use the `observableMethods` option to make it more declarative:
``` js
new Vue({
observableMethods: {
submitHandler: 'submitHandler$'
// or with Array shothand: ['submitHandler']
}
})
```
The above will automatically create two things on the instance:
1. A `submitHandler` method which can be bound to in template with `v-on`;
2. A `submitHandler$` observable which will be the stream emitting calls to `submitHandler`.
[example](https://github.com/vuejs/vue-rx/blob/master/example/counter-function.html)
### Caveats
You cannot use the `watch` option to watch subscriptions, because it is processed before the subscriptions are set up. But you can use `$watch` in the `created` hook instead.
### Example
See `/examples` for some simple examples.
### License
[MIT](http://opensource.org/licenses/MIT)