Skip to content

Instantly share code, notes, and snippets.

@AndrewNatoli
Last active June 6, 2022 03:08
Show Gist options
  • Save AndrewNatoli/62fa6665f57e647e2593ab939c7bc683 to your computer and use it in GitHub Desktop.
Save AndrewNatoli/62fa6665f57e647e2593ab939c7bc683 to your computer and use it in GitHub Desktop.
HTTP requests with RxJS and Svelte

👋

Overview

Working on learning RxJS for fun and since Svelte is able to use observables directly in its templates, it makes for a great pairing!

For several years I've had the convenience of using react-query which, among many things, has a core set of features I like:

  1. It establishes a pattern for managing your queries and mutations.
  2. It will prevent the same GET request from firing multiple times even if there are multiple components using it.
  3. Data is cached until you want new data (you can specify other rules and strategies around this but I'm not interested in them for this project).

This code is a rough draft of implementing those key points of functionality with RxJS. This code could, in theory, later be reduced down and made easily re-usable for many requests.

api.js

refreshRepos calls next on the _refreshRepos Subject. It sends the date because... I decided it would.

repos$ works something like this:

  • We use concat so initially we'll set loading: true whenever we're fetching data.
  • We then make a request to the repos endpoint, grab the response, then pass it along as data while setting loading: false.
  • Using scan lets us keep data set to its previous value so we have the option of only showing a loading indicator on initial load.
  • I'm using shareReplay(1) to cover a few things:
    • Any components subscribing to repos$ at the same time will not trigger an extra network call. All subscribers will receive the result of the first call until _refreshRepos.next() is called again.
    • Any components that mount after completion of the first finished network call will receive the cached result rather than triggering a new call. This may be inconvenient in some situations but it suits what I wanted from this example. Feel free to adjust the code and share your variant.
  • I found this functionality was possible by nesting the ajax call inside of switchMap. Prior to that I couldn't get the network call to fire again when clicking the button.

MyComponent.svelte

We import repos$ and refreshRepos. There's a button that calls refreshRepos.

If we have data, list the repos! Even if we're re-loading; the "loading" message will only be displayed on initial load so we can refresh data in the background without having the list flash (disappear then reappear) to the user.

Other findings

Throttle requests

You can use throttleTime to limit how often _refreshRepos will be called. This will not defer requests, it will prohibit requests by ignoring items from _refreshRepos during the specified time window.

export const repos$ = _refreshRepos.pipe(
  throttleTime(2000), // Two seconds
  switchMap(() =>
    ...
import { tag } from 'rxjs-spy/operators';
import { ajax } from 'rxjs/ajax';
import {
BehaviorSubject,
concat,
mergeAll,
pluck,
shareReplay,
switchMap,
take,
of,
map,
tap,
toArray,
scan,
} from 'rxjs';
const _refreshRepos = new BehaviorSubject(new Date());
export const repos$ = _refreshRepos.pipe(
switchMap(() =>
concat(
of({ loading: true }),
ajax('https://api.github.com/orgs/opencltbrigade/repos').pipe(
pluck('response'),
map((data) => {
return {
loading: false,
data,
};
})
)
)
),
scan(
(state, newState) => {
return {
...state,
...newState,
};
},
{ loading: false, data: undefined }
),
shareReplay(1),
tap(console.log),
tag('repos')
);
export const refreshRepos = () => {
_refreshRepos.next(new Date());
};
<script>
import { refreshRepos, repos$ } from './api';
</script>
<div class="flex flex-col">
<button on:click={refreshRepos} class="bg-orange-500 border-orange-700 text-white p-4 m-4">Fetch Repos</button>
<div class="my-4">
{#if $repos$?.data}
<ul>
{#each $repos$?.data as repo (repo.id)}
<li>{repo?.id} - {repo?.name}</li>
{/each}
</ul>
{:else if $repos$?.loading}
Fetching initial results
{/if}
</div>
</div>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment