An open API service indexing awesome lists of open source software.

https://github.com/dirkluijk/loadable.ts

Type-safe loading states for TypeScript
https://github.com/dirkluijk/loadable.ts

Last synced: 9 days ago
JSON representation

Type-safe loading states for TypeScript

Awesome Lists containing this project

README

          

# Loadable.ts

> Type-safe loading states for TypeScript

[![NPM version](http://img.shields.io/npm/v/loadable.ts.svg?style=flat-square)](https://www.npmjs.com/package/loadable.ts)
[![NPM downloads](http://img.shields.io/npm/dm/loadable.ts.svg?style=flat-square)](https://www.npmjs.com/package/loadable.ts)
[![Build status](https://github.com/dirkluijk/loadable.ts/actions/workflows/main.yml/badge.svg?branch=master)](https://github.com/dirkluijk/loadable.ts/actions/workflows/main.yml)
[![All Contributors](https://img.shields.io/badge/all_contributors-2-orange.svg?style=flat-square)](#contributors-)

## Overview

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

## Getting started 🌩

##### npm

```
npm install loadable.ts
```

##### yarn

```
yarn add loadable.ts
```

## Type-safe loading interfaces

It introduces a `Loadable` type that represents three possible states,
`Loading`, `Success` or `Failed`.

```typescript
type Loadable = Loading | Success | Failed;
```

Plain TypeScript example:

```typescript
import { LOADING, success, failed, Loadable } from 'loadable.ts';

function getFoo(): Loadable {
if (...) {
return LOADING; // returns a `Loading`
} else if (...) {
return success(...); // returns a `Success`
} else {
return failed(...); // returns a `Failed`
}
}

const foo: Loadable = getFoo();

if (foo.loading) {
// will infer to `Loading`
console.log('Loading...');
} else if (foo.success) {
// will infer to `Success` and provide value object
console.log(`Result: ${foo.value}`);
} else {
// will infer to `Failed` and provide error object
console.error(`Result: ${foo.error}`);
}
```

## Type-guards

To improve semantics and code readability, we provide the following type-guards:

* `isLoading()`
* `isSuccess()`
* `isFailed()`

```typescript
import { isLoading, isSuccess, Loadable } from 'loadable.ts';

const foo: Loadable = getFoo();

if (isLoading(foo)) {
// will infer to `Loading`
console.log('Loading...');
} else if (isSuccess(foo)) {
// will infer to `Success` and provide value object
console.log(`Result: ${foo.value}`);
} else {
// will infer to `Failed` and provide error object
console.error(`Result: ${foo.error}`);
}
```

## Usage with RxJS

We provide a `mapToLoadable()` operator for RxJS, which can be useful for async streams like HTTP responses.

* It prepends the upstream `Observable` with a `Loading` state
* It maps each result `T` in `Observable` to a `Success` state
* It catches and maps each error `E` in the `Observable` to a `Failed` state

Example:

```typescript
function loadFoo(): Observable {
// ...
}

const foo$: Observable> = 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 for `filter(isFailed)` and `map((it) => it.error)`
* `onSuccess()`: shorthand for `filter(isSuccess)` and `map((it) => it.value)`
* `mapSuccess(mapFn)`: allows you to map the `value` when it is `Success`

## Structural directives for Angular

We also provide three useful structural directives for Angular.

They all accept a `Loadable` or `Observable>` input variable.

* `*ifLoaded`: it will show the template when the latest value is in `Loading` state
* `*ifFailed`: it will show the template when the latest value is in `Failed` state
* `*ifSuccess`: it will show the template when the latest value is in `Success` state

Example usage:

```typescript
interface Foo {
name: string;
}

@Component({
/* ... */
})
class MyComponent{
public foo$: Observable> = ...;

/* ... */
}
```

```html



{{ error }}



{{ foo.name }}

```

## Monads

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)`

Example usage:

```typescript
interface Foo {
loadableBar: Loadable;
}

interface Bar {
name: string;
}

const foo: Loadable = ...;

const barName: Loadable = 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 : foo

```

## Contributors ✨

Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):


Dirk Luijk
Dirk Luijk

💻 📖
Daan Scheerens
Daan Scheerens

🤔

This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!