Skip to content

Commit 2a3768b

Browse files
regouyyx990803
authored andcommitted
Add feature eventToObservable (#45)
* add feature eventToObservable * .take(1) * better example * custom event support * Fix : Only subscriptions can be unsubed
1 parent e8bdc2d commit 2a3768b

File tree

8 files changed

+134
-23
lines changed

8 files changed

+134
-23
lines changed

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,30 @@ vm.$watchAsObservable('a')
183183

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

186+
#### `$eventToObservable(event)`
187+
188+
> This feature requires RxJS.
189+
190+
Convert vue.$on (including lifecycle events) to Observables. The emitted value is in the format of `{ name, msg }`:
191+
192+
``` js
193+
var vm = new Vue({
194+
created () {
195+
this.$eventToObservable('customEvent')
196+
.subscribe((event) => console.log(event.name,event.msg))
197+
}
198+
})
199+
200+
// vm.$once vue-rx version
201+
this.$eventToObservable('customEvent')
202+
.take(1)
203+
204+
// Another way to auto unsub:
205+
let beforeDestroy$ = this.$eventToObservable('hook:beforeDestroy').take(1)
206+
Rx.Observable.interval(500)
207+
.takeUntil(beforeDestroy$)
208+
```
209+
186210
#### `$subscribeTo(observable, next, error, complete)`
187211

188212
This is a prototype method added to instances. You can use it to subscribe to an observable, but let VueRx manage the dispose/unsubscribe.

example/counter-simple.html

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,31 @@
66
<div>{{ count }}</div>
77
<button v-stream:click="plus$">+</button>
88
<button v-stream:click="minus$">-</button>
9+
<my-component v-stream:more="more$"></my-component>
910
</div>
1011

1112
<script>
13+
Vue.component('my-component', {
14+
template: '<button v-on:click="more">Iwant more!</button>',
15+
methods:{
16+
more:function(){
17+
this.$emit('more',100)
18+
}
19+
}
20+
})
21+
1222
new Vue({
1323
el: '#app',
1424

1525
// declare dom stream Subjects
16-
domStreams: ['plus$', 'minus$'],
26+
domStreams: ['plus$', 'minus$','more$'],
1727

1828
subscriptions () {
1929
var plus$ = this.plus$.map(() => 1)
2030
var minus$ = this.minus$.map(() => -1)
31+
var more$ = this.more$.map(() => 100)
2132
var count$ = Rx.Observable
22-
.merge(plus$, minus$)
33+
.merge(plus$, minus$, more$)
2334
.startWith(0)
2435
.scan((total, change) => total + change)
2536

@@ -28,4 +39,4 @@
2839
}
2940
}
3041
})
31-
</script>
42+
</script>

src/directives/stream.js

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,6 @@ export default {
1111
const event = binding.arg
1212
const streamName = binding.expression
1313

14-
if (!Rx.Observable.fromEvent) {
15-
warn(
16-
`No 'fromEvent' method on Observable class. ` +
17-
`v-stream directive requires Rx.Observable.fromEvent method. ` +
18-
`Try import 'rxjs/add/observable/fromEvent' for ${streamName}`,
19-
vnode.context
20-
)
21-
return
22-
}
2314
if (isSubject(handle)) {
2415
handle = { subject: handle }
2516
} else if (!handle || !isSubject(handle.subject)) {
@@ -34,17 +25,36 @@ export default {
3425

3526
const subject = handle.subject
3627
const next = (subject.next || subject.onNext).bind(subject)
37-
let fromEventArgs = handle.options ? [el, event, handle.options] : [el, event]
38-
handle.subscription = Rx.Observable.fromEvent(...fromEventArgs).subscribe(e => {
39-
next({
40-
event: e,
41-
data: handle.data
28+
29+
if (vnode.componentInstance) {
30+
handle.subscription = vnode.componentInstance.$eventToObservable(event).subscribe(e => {
31+
next({
32+
event: e,
33+
data: handle.data
34+
})
35+
})
36+
} else {
37+
if (!Rx.Observable.fromEvent) {
38+
warn(
39+
`No 'fromEvent' method on Observable class. ` +
40+
`v-stream directive requires Rx.Observable.fromEvent method. ` +
41+
`Try import 'rxjs/add/observable/fromEvent' for ${streamName}`,
42+
vnode.context
43+
)
44+
return
45+
}
46+
let fromEventArgs = handle.options ? [el, event, handle.options] : [el, event]
47+
handle.subscription = Rx.Observable.fromEvent(...fromEventArgs).subscribe(e => {
48+
next({
49+
event: e,
50+
data: handle.data
51+
})
4252
})
43-
})
4453

45-
// store handle on element with a unique key for identifying
46-
// multiple v-stream directives on the same node
47-
;(el._rxHandles || (el._rxHandles = {}))[getKey(binding)] = handle
54+
// store handle on element with a unique key for identifying
55+
// multiple v-stream directives on the same node
56+
;(el._rxHandles || (el._rxHandles = {}))[getKey(binding)] = handle
57+
}
4858
},
4959

5060
update (el, binding) {

src/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import streamDirective from './directives/stream'
44
import watchAsObservable from './methods/watchAsObservable'
55
import fromDOMEvent from './methods/fromDOMEvent'
66
import subscribeTo from './methods/subscribeTo'
7+
import eventToObservable from './methods/eventToObservable'
78

89
export default function VueRx (Vue, Rx) {
910
install(Vue, Rx)
@@ -12,6 +13,7 @@ export default function VueRx (Vue, Rx) {
1213
Vue.prototype.$watchAsObservable = watchAsObservable
1314
Vue.prototype.$fromDOMEvent = fromDOMEvent
1415
Vue.prototype.$subscribeTo = subscribeTo
16+
Vue.prototype.$eventToObservable = eventToObservable
1517
}
1618

1719
// auto install

src/methods/eventToObservable.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { Rx, hasRx } from '../util'
2+
3+
/**
4+
* @see {@link https://vuejs.org/v2/api/#vm-on}
5+
* @param {String||Array} evtName Event name
6+
* @return {Observable} Event stream
7+
*/
8+
export default function eventToObservable (evtName) {
9+
if (!hasRx()) {
10+
return
11+
}
12+
const vm = this
13+
let evtNames = Array.isArray(evtName) ? evtName : [evtName]
14+
const obs$ = Rx.Observable.create(observer => {
15+
let eventPairs = evtNames.map(name => {
16+
let callback = msg => observer.next({name, msg})
17+
vm.$on(name, callback)
18+
return {name, callback}
19+
})
20+
return () => {
21+
// Only remove the specific callback
22+
eventPairs.forEach(pair => vm.$off(pair.name, pair.callback))
23+
}
24+
})
25+
26+
return obs$
27+
}

src/methods/fromDOMEvent.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,5 @@ export default function fromDOMEvent (selector, event) {
2727
})
2828
})
2929

30-
;(vm._obSubscriptions || (vm._obSubscriptions = [])).push(obs$)
3130
return obs$
3231
}

src/methods/watchAsObservable.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,5 @@ export default function watchAsObservable (expOrFn, options) {
2929
})
3030
})
3131

32-
;(vm._obSubscriptions || (vm._obSubscriptions = [])).push(obs$)
3332
return obs$
3433
}

test/test.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,3 +284,42 @@ test('$subscribeTo()', () => {
284284
next(2)
285285
expect(results).toEqual([1]) // should not trigger anymore
286286
})
287+
288+
289+
test('$eventToObservable()', done => {
290+
let calls = 0;
291+
const vm = new Vue({
292+
created(){
293+
let ob = this.$eventToObservable('ping')
294+
.subscribe(function (event) {
295+
expect(event.name).toEqual('ping');
296+
expect(event.msg).toEqual('ping message');
297+
calls++
298+
});
299+
}
300+
});
301+
vm.$emit('ping','ping message');
302+
303+
nextTick(()=>{
304+
vm.$destroy();
305+
//Should not emit
306+
vm.$emit('pong','pong message');
307+
expect(calls).toEqual(1);
308+
done()
309+
});
310+
});
311+
312+
313+
test('$eventToObservable() with lifecycle hooks', done => {
314+
const vm = new Vue({
315+
created(){
316+
this.$eventToObservable('hook:beforeDestroy')
317+
.subscribe(function (event) {
318+
done(event)
319+
});
320+
}
321+
});
322+
nextTick(()=>{
323+
vm.$destroy()
324+
})
325+
});

0 commit comments

Comments
 (0)