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
- Host: GitHub
- URL: https://github.com/dirkluijk/loadable.ts
- Owner: dirkluijk
- License: mit
- Created: 2020-08-30T14:31:06.000Z (almost 6 years ago)
- Default Branch: master
- Last Pushed: 2024-01-05T14:36:29.000Z (over 2 years ago)
- Last Synced: 2026-04-29T09:15:57.721Z (3 months ago)
- Language: TypeScript
- Size: 846 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# Loadable.ts
> Type-safe loading states for TypeScript
[](https://www.npmjs.com/package/loadable.ts)
[](https://www.npmjs.com/package/loadable.ts)
[](https://github.com/dirkluijk/loadable.ts/actions/workflows/main.yml)
[](#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
💻 📖

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