From 5f3d02618ac3c427a26fe1f25e786e52dcb053c8 Mon Sep 17 00:00:00 2001 From: Vincent Schramer <885196+scvnc@users.noreply.github.com> Date: Tue, 3 May 2022 11:32:19 -0500 Subject: [PATCH] feat(rxjs): Correct useObservable type signature and provide means of creating a non-undefined Ref (#1551) --- packages/rxjs/useObservable/index.test.ts | 56 +++++++++++++++++++++++ packages/rxjs/useObservable/index.ts | 18 +++++--- packages/rxjs/useSubject/index.ts | 2 +- 3 files changed, 69 insertions(+), 7 deletions(-) create mode 100644 packages/rxjs/useObservable/index.test.ts diff --git a/packages/rxjs/useObservable/index.test.ts b/packages/rxjs/useObservable/index.test.ts new file mode 100644 index 00000000000..f82e89f8b34 --- /dev/null +++ b/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 + let delayedEmissionStream: Observable + + beforeEach(() => { + testDataSource = new BehaviorSubject({ 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 +} diff --git a/packages/rxjs/useObservable/index.ts b/packages/rxjs/useObservable/index.ts index fd38dc9d70e..6bc263b9743 100644 --- a/packages/rxjs/useObservable/index.ts +++ b/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 { onError?: (err: any) => void + /** + * The value that should be set if the observable has not emitted. + */ + initialValue?: I | undefined } -export function useObservable(observable: Observable, options?: UseObservableOptions): Readonly> { - const value = ref() +export function useObservable( + observable: Observable, + options?: UseObservableOptions): Readonly> { + const value = ref(options?.initialValue) const subscription = observable.subscribe({ - next: val => (value.value = val), + next: val => (value.value = (val as UnwrapRef)), error: options?.onError, }) tryOnScopeDispose(() => { subscription.unsubscribe() }) - return value as Readonly> + return value as Readonly> } diff --git a/packages/rxjs/useSubject/index.ts b/packages/rxjs/useSubject/index.ts index 23e813e764c..41818e71768 100644 --- a/packages/rxjs/useSubject/index.ts +++ b/packages/rxjs/useSubject/index.ts @@ -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 extends Omit, 'initialValue'> { } export function useSubject(subject: BehaviorSubject, options?: UseSubjectOptions): Ref