Type-safe loading states for TypeScript
This is a small util library to design type-safe loading states.
It includes:
- Type-safe loading interfaces
- Type-guards for loadable states
- Useful operators for RxJS
- Structural directives for Angular
- Monad helpers
npm install loadable.ts
yarn add loadable.ts
It introduces a Loadable<T, E> type that represents three possible states,
Loading, Success<T> or Failed<E>.
type Loadable<T, E = unknown> = Loading | Success<T> | Failed<E>;Plain TypeScript example:
import { LOADING, success, failed, Loadable } from 'loadable.ts';
function getFoo(): Loadable<Foo> {
if (...) {
return LOADING; // returns a `Loading`
} else if (...) {
return success(...); // returns a `Success<Foo>`
} else {
return failed(...); // returns a `Failed`
}
}
const foo: Loadable<Foo> = getFoo();
if (foo.loading) {
// will infer to `Loading`
console.log('Loading...');
} else if (foo.success) {
// will infer to `Success<Foo>` and provide value object
console.log(`Result: ${foo.value}`);
} else {
// will infer to `Failed` and provide error object
console.error(`Result: ${foo.error}`);
}To improve semantics and code readability, we provide the following type-guards:
isLoading()isSuccess()isFailed()
import { isLoading, isSuccess, Loadable } from 'loadable.ts';
const foo: Loadable<Foo> = getFoo();
if (isLoading(foo)) {
// will infer to `Loading`
console.log('Loading...');
} else if (isSuccess(foo)) {
// will infer to `Success<Foo>` and provide value object
console.log(`Result: ${foo.value}`);
} else {
// will infer to `Failed` and provide error object
console.error(`Result: ${foo.error}`);
}We provide a mapToLoadable() operator for RxJS, which can be useful for async streams like HTTP responses.
- It prepends the upstream
Observablewith aLoadingstate - It maps each result
TinObservable<T>to aSuccess<T>state - It catches and maps each error
Ein theObservableto aFailed<E>state
Example:
function loadFoo(): Observable<Foo> {
// ...
}
const foo$: Observable<Loadable<Foo>> = loadFoo().pipe(mapToLoadable());
// makes use of the provided type-guards
const showSpinner$ = foo$.pipe(map(isLoading));
const showError$ = foo$.pipe(map(isFailed));
const fooValue$ = foo$.pipe(filter(isSuccess), map(it => it.value));Furthermore, we provide the following additional RxJS operators:
onFailed(): shorthand forfilter(isFailed)andmap((it) => it.error)onSuccess(): shorthand forfilter(isSuccess)andmap((it) => it.value)mapSuccess(mapFn): allows you to map thevaluewhen it isSuccess
We also provide three useful structural directives for Angular.
They all accept a Loadable<T> or Observable<Loadable<T>> input variable.
*ifLoaded: it will show the template when the latest value is inLoadingstate*ifFailed: it will show the template when the latest value is inFailedstate*ifSuccess: it will show the template when the latest value is inSuccessstate
Example usage:
interface Foo {
name: string;
}
@Component({
/* ... */
})
class MyComponent{
public foo$: Observable<Loadable<Foo>> = ...;
/* ... */
}<!-- loading state -->
<div class="loading" *ifLoading="foo$"></div>
<!-- failed state -->
<div class="error" *ifFailed="foo$"></div>
<div class="error" *ifFailed="let error of foo$">
{{ error }}
</div>
<!-- success state -->
<div class="result" *ifSuccess="foo$"></div>
<div class="result" *ifSuccess="let foo of foo$">
{{ foo.name }}
</div>If you want to apply operations to a Loadable, without the need to unwrap it, you could use the monad() helper function.
It returns the monadic variant LoadableMonad which currently provides the following operations:
map(fn: (value: T) => R)flatMap(fn: (value: T) => Loadable<R>)
Example usage:
interface Foo {
loadableBar: Loadable<Bar>;
}
interface Bar {
name: string;
}
const foo: Loadable<Foo> = ...;
const barName: Loadable<string> = monad(foo)
.flatMap(foo => foo.loadableBar)
.map(bar => bar.name);
// this would be the same as:
const barName = isSuccess(foo) ? isSuccess(foo.value.loadableBar) ? success(foo.value.loadableBar.value.name) : foo.value.loadableBar : fooThanks goes to these wonderful people (emoji key):
Dirk Luijk 💻 📖 |
Daan Scheerens 🤔 |
This project follows the all-contributors specification. Contributions of any kind welcome!