diff --git a/README.md b/README.md index a33ced4..8d05c2b 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/example/counter-simple.html b/example/counter-simple.html index 75b981c..c996d52 100644 --- a/example/counter-simple.html +++ b/example/counter-simple.html @@ -6,20 +6,31 @@
{{ count }}
+ + \ No newline at end of file diff --git a/src/directives/stream.js b/src/directives/stream.js index 8a92f46..47417ad 100644 --- a/src/directives/stream.js +++ b/src/directives/stream.js @@ -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)) { @@ -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) { diff --git a/src/index.js b/src/index.js index 60961b9..5e5ed78 100644 --- a/src/index.js +++ b/src/index.js @@ -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) @@ -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 diff --git a/src/methods/eventToObservable.js b/src/methods/eventToObservable.js new file mode 100644 index 0000000..37b1c67 --- /dev/null +++ b/src/methods/eventToObservable.js @@ -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$ +} diff --git a/src/methods/fromDOMEvent.js b/src/methods/fromDOMEvent.js index f862084..271db0e 100644 --- a/src/methods/fromDOMEvent.js +++ b/src/methods/fromDOMEvent.js @@ -27,6 +27,5 @@ export default function fromDOMEvent (selector, event) { }) }) - ;(vm._obSubscriptions || (vm._obSubscriptions = [])).push(obs$) return obs$ } diff --git a/src/methods/watchAsObservable.js b/src/methods/watchAsObservable.js index 778d65c..cdf4a14 100644 --- a/src/methods/watchAsObservable.js +++ b/src/methods/watchAsObservable.js @@ -29,6 +29,5 @@ export default function watchAsObservable (expOrFn, options) { }) }) - ;(vm._obSubscriptions || (vm._obSubscriptions = [])).push(obs$) return obs$ } diff --git a/test/test.js b/test/test.js index 6e6bb40..f09eb4b 100644 --- a/test/test.js +++ b/test/test.js @@ -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() + }) +});