Skip to content

Commit

Permalink
feat(rxjs): Correct useObservable type signature and provide means of…
Browse files Browse the repository at this point in the history
… creating a non-undefined Ref (#1551)
  • Loading branch information
scvnc committed May 3, 2022
1 parent 7aaebb3 commit 5f3d026
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 7 deletions.
56 changes: 56 additions & 0 deletions packages/rxjs/useObservable/index.test.ts
@@ -0,0 +1,56 @@
import type { Observable } from 'rxjs'
import { BehaviorSubject } from 'rxjs'
import { delay } from 'rxjs/operators'
import { useObservable } from '.'

describe('useObservable', () => {
let testDataSource: BehaviorSubject<TestPerson>
let delayedEmissionStream: Observable<TestPerson>

beforeEach(() => {
testDataSource = new BehaviorSubject<TestPerson>({ fullName: 'Mario Mario' })
delayedEmissionStream = testDataSource.pipe(delay(0))
})

describe('when initialValue is not provided', () => {
it('should set the ref\'s value to undefined before the stream has emitted', async() => {
const testPerson = useObservable(delayedEmissionStream)
expect(testPerson.value).toBe(undefined)
})

it('should set the ref\'s value from the data emitted on the stream', async() => {
const testPerson = useObservable(delayedEmissionStream)

// wait for next tick, allowing RxJS to emit the value
await new Promise(resolve => setTimeout(resolve, 0))

// Notice optional chaining operator required
expect(testPerson.value?.fullName).toEqual('Mario Mario')
})
})

describe('when initialValue is provided', () => {
it('should set the ref\'s value to initialData before the stream has emitted', () => {
const testPerson = useObservable(delayedEmissionStream, { initialValue: { fullName: 'I don\'t know yet!' } })

// Notice how we do not need the optional chaining to access fullName
expect(testPerson.value.fullName).toBe('I don\'t know yet!')
})

it('should set the ref\'s value from the data emitted on the stream', async() => {
const testPerson = useObservable(delayedEmissionStream, { initialValue: { fullName: 'I don\'t know yet!' } })
// wait for next tick, allowing RxJS to emit the value
await new Promise(resolve => setTimeout(resolve, 0))

// Notice how we do not need the optional chaining to access fullName
expect(testPerson.value.fullName).toBe('Mario Mario')
})
})
})

/**
* Used for this test to examine optional chaining and typescript type computation.
*/
interface TestPerson {
fullName: string
}
18 changes: 12 additions & 6 deletions packages/rxjs/useObservable/index.ts
@@ -1,20 +1,26 @@
import type { Observable } from 'rxjs'
import type { Ref } from 'vue-demi'
import type { Ref, UnwrapRef } from 'vue-demi'
import { ref } from 'vue-demi'
import { tryOnScopeDispose } from '@vueuse/shared'

export interface UseObservableOptions {
export interface UseObservableOptions<I> {
onError?: (err: any) => void
/**
* The value that should be set if the observable has not emitted.
*/
initialValue?: I | undefined
}

export function useObservable<H>(observable: Observable<H>, options?: UseObservableOptions): Readonly<Ref<H>> {
const value = ref<H | undefined>()
export function useObservable<H, I = undefined>(
observable: Observable<H>,
options?: UseObservableOptions<I | undefined>): Readonly<Ref<H | I>> {
const value = ref<H | I | undefined>(options?.initialValue)
const subscription = observable.subscribe({
next: val => (value.value = val),
next: val => (value.value = (val as UnwrapRef<H>)),
error: options?.onError,
})
tryOnScopeDispose(() => {
subscription.unsubscribe()
})
return value as Readonly<Ref<H>>
return value as Readonly<Ref<H | I>>
}
2 changes: 1 addition & 1 deletion packages/rxjs/useSubject/index.ts
Expand Up @@ -5,7 +5,7 @@ import { ref, watch } from 'vue-demi'
import { tryOnScopeDispose } from '@vueuse/shared'
import type { UseObservableOptions } from '../useObservable'

export interface UseSubjectOptions extends UseObservableOptions {
export interface UseSubjectOptions<I = undefined> extends Omit<UseObservableOptions<I>, 'initialValue'> {
}

export function useSubject<H>(subject: BehaviorSubject<H>, options?: UseSubjectOptions): Ref<H>
Expand Down

0 comments on commit 5f3d026

Please sign in to comment.