My first major contribution: More rxjs in vue-use
Tags: opensource, vue, vueuse, rxjs
Published: 2023-10-28 16:31:41

Let me tell you the story of my first (actually relevant) major contribution to an Open Source project: adding more [rxjs](https://rxjs.dev/guide/overview) in [vue-use](https://vueuse.org/guide/).

## My approach to Computer Science

I chose Computer Science because I like writing code, and I like solving problems. My approach to open source and Computer Science as a whole is the following:

**Solve a problem, don't create a problem just to solve it**

## My problem

I was rewriting [my music player](https://github.com/Voltra/vuelectron-music) and had to migrate the existing code from Vue 2 to Vue 3. I also had to, you know, actually finish the damn thing this time.

One thing I like to do when using Vue (or any other framework) is to completely decouple the business logic from the "application logic". I also like to completely decouple the "framework logic" from the business logic.

As such, I had written an `AudioPlayer` class. Trying to integrate the pieces together, I noticed something: I need an `HTMLAudioElement` to construct my `AudioPlayer`, but I can only ever get `Ref<HTMLAudioElement|null>` if I don't want to use directives.

That problem was easy enough to solve: make a `computed` property and wrap it nicely into a `ReadonlyRef<AudioPlayer|null>`.

Then the real issue came up: `useObservable` only accepts `Observable<T>` as input. I have a `ReadonlyRef<AudioPlayer|null>` which can give me a `ReadonlyRef<Observable<T>>`. I can't make that work with the existing composition functions, but I desperately want to!

So what did I do? I rolled my own!

## What's new?

Introducing [`useExtractedObservable`](https://vueuse.org/rxjs/useExtractedObservable/) and [`watchExtractedObservable`](https://vueuse.org/rxjs/watchExtractedObservable/).

## What do they do?

Precisely solve one problem (in two similar yet distinct ways): **The lack of interoperability between Vue's [composables](https://vuejs.org/guide/reusability/composables.html) and RxJS's [observables](https://rxjs.dev/guide/observable).**

## How it went

I had wrote a first draft of them for myself, and thought:

> If I have that problem, others will.

And thus I forked `vue-use`, looked at the implementation of `useObservable` and [`watch`](https://vuejs.org/api/reactivity-core.html#watch), and adapted them to make the new composables.

## useExtractedObservable

Is a fancier version of `useObservable` that, as the name suggests, extracts the observable from something else (aka [watch sources](https://vuejs.org/guide/essentials/watchers.html#watch-source-types)).

You map your extracted data into an observable, and turn that observable as a [`ref`](https://vuejs.org/api/reactivity-core.html#ref) (well, technically really a [`shallowRef`](https://vuejs.org/api/reactivity-advanced.html#shallowref) for performance reasons).

```typescript
import { ref } from 'vue'
import { useExtractedObservable } from '@vueuse/rxjs'
import { interval } from 'rxjs'
import { mapTo, scan, startWith, takeWhile } from 'rxjs/operators'
// setup()
const start = ref<number>()
const count = useExtractedObservable(
  start,
  (start) => {
    return interval(1000).pipe(
      mapTo(1),
      startWith(start),
      scan((total, next) => next + total),
      takeWhile((num) => num < 10),
    )
  },
  {},
  {
    immediate: false,
  },
)
```

## watchExtractedObservable

Is the love child of `watch` and `useExtractedObservable`. Instead of getting the new values in a `ref`, you instead get to react to them via a callback.

```typescript
import { computed, ref } from 'vue'
import { watchExtractedObservable } from '@vueuse/rxjs'
import { AudioPlayer } from '../my/libs/AudioPlayer'
// setup()
const audio = ref<HTMLAudioElement>()
const player = computed(() => (audio.value ? new AudioPlayer(audio) : null))
const state = reactive({
  progress: 0,
})
watchExtractedObservable(
  player,
  (p) => p.progress$,
  (percentage) => {
    state.progress = percentage * 100
  },
)
```