Skip to content

Add feature eventToObservable #45

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 13, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,30 @@ vm.$watchAsObservable('a')

The optional `options` object accepts the same options as `vm.$watch`.

#### `$eventToObservable(event)`

> This feature requires RxJS.

Convert vue.$on (including lifecycle events) to Observables. The emitted value is in the format of `{ name, msg }`:

``` js
var vm = new Vue({
created () {
this.$eventToObservable('customEvent')
.subscribe((event) => console.log(event.name,event.msg))
}
})

// vm.$once vue-rx version
this.$eventToObservable('customEvent')
.take(1)

// Another way to auto unsub:
let beforeDestroy$ = this.$eventToObservable('hook:beforeDestroy').take(1)
Rx.Observable.interval(500)
.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.
Expand Down
17 changes: 14 additions & 3 deletions example/counter-simple.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,31 @@
<div>{{ count }}</div>
<button v-stream:click="plus$">+</button>
<button v-stream:click="minus$">-</button>
<my-component v-stream:more="more$"></my-component>
</div>

<script>
Vue.component('my-component', {
template: '<button v-on:click="more">Iwant more!</button>',
methods:{
more:function(){
this.$emit('more',100)
}
}
})

new Vue({
el: '#app',

// declare dom stream Subjects
domStreams: ['plus$', 'minus$'],
domStreams: ['plus$', 'minus$','more$'],

subscriptions () {
var plus$ = this.plus$.map(() => 1)
var minus$ = this.minus$.map(() => -1)
var more$ = this.more$.map(() => 100)
var count$ = Rx.Observable
.merge(plus$, minus$)
.merge(plus$, minus$, more$)
.startWith(0)
.scan((total, change) => total + change)

Expand All @@ -28,4 +39,4 @@
}
}
})
</script>
</script>
46 changes: 28 additions & 18 deletions src/directives/stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,6 @@ export default {
const event = binding.arg
const streamName = binding.expression

if (!Rx.Observable.fromEvent) {
warn(
`No 'fromEvent' method on Observable class. ` +
`v-stream directive requires Rx.Observable.fromEvent method. ` +
`Try import 'rxjs/add/observable/fromEvent' for ${streamName}`,
vnode.context
)
return
}
if (isSubject(handle)) {
handle = { subject: handle }
} else if (!handle || !isSubject(handle.subject)) {
Expand All @@ -34,17 +25,36 @@ export default {

const subject = handle.subject
const next = (subject.next || subject.onNext).bind(subject)
let fromEventArgs = handle.options ? [el, event, handle.options] : [el, event]
handle.subscription = Rx.Observable.fromEvent(...fromEventArgs).subscribe(e => {
next({
event: e,
data: handle.data

if (vnode.componentInstance) {
handle.subscription = vnode.componentInstance.$eventToObservable(event).subscribe(e => {
next({
event: e,
data: handle.data
})
})
} else {
if (!Rx.Observable.fromEvent) {
warn(
`No 'fromEvent' method on Observable class. ` +
`v-stream directive requires Rx.Observable.fromEvent method. ` +
`Try import 'rxjs/add/observable/fromEvent' for ${streamName}`,
vnode.context
)
return
}
let fromEventArgs = handle.options ? [el, event, handle.options] : [el, event]
handle.subscription = Rx.Observable.fromEvent(...fromEventArgs).subscribe(e => {
next({
event: e,
data: handle.data
})
})
})

// store handle on element with a unique key for identifying
// multiple v-stream directives on the same node
;(el._rxHandles || (el._rxHandles = {}))[getKey(binding)] = handle
// store handle on element with a unique key for identifying
// multiple v-stream directives on the same node
;(el._rxHandles || (el._rxHandles = {}))[getKey(binding)] = handle
}
},

update (el, binding) {
Expand Down
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import streamDirective from './directives/stream'
import watchAsObservable from './methods/watchAsObservable'
import fromDOMEvent from './methods/fromDOMEvent'
import subscribeTo from './methods/subscribeTo'
import eventToObservable from './methods/eventToObservable'

export default function VueRx (Vue, Rx) {
install(Vue, Rx)
Expand All @@ -12,6 +13,7 @@ export default function VueRx (Vue, Rx) {
Vue.prototype.$watchAsObservable = watchAsObservable
Vue.prototype.$fromDOMEvent = fromDOMEvent
Vue.prototype.$subscribeTo = subscribeTo
Vue.prototype.$eventToObservable = eventToObservable
}

// auto install
Expand Down
27 changes: 27 additions & 0 deletions src/methods/eventToObservable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Rx, hasRx } from '../util'

/**
* @see {@link https://vuejs.org/v2/api/#vm-on}
* @param {String||Array} evtName Event name
* @return {Observable} Event stream
*/
export default function eventToObservable (evtName) {
if (!hasRx()) {
return
}
const vm = this
let evtNames = Array.isArray(evtName) ? evtName : [evtName]
const obs$ = Rx.Observable.create(observer => {
let eventPairs = evtNames.map(name => {
let callback = msg => observer.next({name, msg})
vm.$on(name, callback)
return {name, callback}
})
return () => {
// Only remove the specific callback
eventPairs.forEach(pair => vm.$off(pair.name, pair.callback))
}
})

return obs$
}
1 change: 0 additions & 1 deletion src/methods/fromDOMEvent.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,5 @@ export default function fromDOMEvent (selector, event) {
})
})

;(vm._obSubscriptions || (vm._obSubscriptions = [])).push(obs$)
return obs$
}
1 change: 0 additions & 1 deletion src/methods/watchAsObservable.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,5 @@ export default function watchAsObservable (expOrFn, options) {
})
})

;(vm._obSubscriptions || (vm._obSubscriptions = [])).push(obs$)
return obs$
}
39 changes: 39 additions & 0 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -284,3 +284,42 @@ test('$subscribeTo()', () => {
next(2)
expect(results).toEqual([1]) // should not trigger anymore
})


test('$eventToObservable()', done => {
let calls = 0;
const vm = new Vue({
created(){
let ob = this.$eventToObservable('ping')
.subscribe(function (event) {
expect(event.name).toEqual('ping');
expect(event.msg).toEqual('ping message');
calls++
});
}
});
vm.$emit('ping','ping message');

nextTick(()=>{
vm.$destroy();
//Should not emit
vm.$emit('pong','pong message');
expect(calls).toEqual(1);
done()
});
});


test('$eventToObservable() with lifecycle hooks', done => {
const vm = new Vue({
created(){
this.$eventToObservable('hook:beforeDestroy')
.subscribe(function (event) {
done(event)
});
}
});
nextTick(()=>{
vm.$destroy()
})
});