rxjs-loading-state
Version:
Eliminates manual state management for loading and error states by transforming Observables into a LoadingState
348 lines (236 loc) • 11.6 kB
Markdown
<center style="max-width: 500px; margin: 0 auto;">
# rxjs-loading-state
[](https://app.circleci.com/pipelines/github/tomraithel/rxjs-loading-state)
[](https://www.npmjs.com/package/rxjs-loading-state)
**rxjs-loading-state** eliminates manual state management for loading and error states by transforming `Observables` into a `LoadingState`.
</center>
## Table of contents:
- [Getting Started](#getting-started)
- [Installation](#installation)
- [TL;DR](#tldr)
- [What is a LoadingStateMachine?](#what-is-a-loading-state)
- [trackLoadingBy operator explained](#trackLoadingBy-operator)
- [API](#api)
- [LoadingStateMachine](#LoadingStateMachine)
- [trackLoadingBy](#trackLoadingBy)
## <a name="getting-started"></a> Getting started
### <a name="installation"></a> How to install?
Installation via NPM:
```bash
npm install rxjs-loading-state --save
```
### <a name="tldr"></a> TL;DR
You create a `LoadingStateMachine` object and connect it to your observable. This object now reflects the loading state of your Observable:
```ts
// Create a new LoadingStateMachine instance
const machine = new LoadingStateMachine<number>();
// Create an Observable that finishes after 1000ms and track it by the machine
const loadData$ = of(42).pipe(delay(1000), trackLoadingBy(machine));
// As long as no one is subscribed, loading state is in "notStarted" state
console.log(machine.state); // "notStarted"
// After subscription, loading state transitions to "loading" state
loadData$.subscribe();
console.log(machine.state); // "loading"
// After 2s the Observable is completed and loadingState transitioned to "success"
setTimeout(() => {
console.log(machine.state); // "success"
}, 2000);
```
### <a name="what-is-a-loading-state"></a> What is a `LoadingStateMachine`?
The `LoadingStateMachine` is a small state-machine that consists of four states it can be in:
| Type | Description |
| ----------------------------- | ------------------------------------------------------------ |
| `LoadingStateName.NotStarted` | Loading has not been started yet. |
| `LoadingStateName.Loading` | Data is getting loaded. Could be the first load or a reload. |
| `LoadingStateName.Error` | An error occurred during loading. |
| `LoadingStateName.Success` | Data has successfully been loaded. |
Transition between these steps can be performed with event methods. This state-chart gives an overview, which event can be called in which state.
<img src="docs/state-machine.png" alt="state-machine" width="600"/>
> Note: If an event is triggered in an invalid state, the state machine rises an exception!
```typescript
const machine = new LoadingStateMachine<string>();
machine.start();
console.log(machine.state); // "loading"
console.log(machine.data); // undefined
machine.succeed("my-data");
console.log(machine.state); // "success"
console.log(machine.data); // "my-data"
machine.succeed("boo"); // throws IllegalStateTransitionError: Transition from success to success not allowed
```
### <a name="trackLoadingBy-operator"></a> The `trackLoadingBy` explained
Although you can manually trigger state changes on a `LoadingStateMachine`, there is a better way to do it.
The `trackLoadingBy` operator connects your state machine to an existing Observable. As soon as a subscription to this Observable starts, the `LoadingStateMachine` instance gets updated automatically and can be used in your view template.
```typescript
const machine = new LoadingStateMachine();
fetchData().pipe(trackLoadingBy(machine)).subscribe();
// later in your render-loop or template
function render() {
if (machine.isLoading()) {
return "loading...";
}
if (machine.isError()) {
return "error: " + machine.error.message;
}
if (machine.isSuccess()) {
return "data fetched: " + machine.data;
}
}
```
## <a name="api"></a> API
<!-- JSDOC START -->
<a name="LoadingStateMachine"></a>
## LoadingStateMachine
**Kind**: global class
- [LoadingStateMachine](#LoadingStateMachine)
- [new LoadingStateMachine()](#new_LoadingStateMachine_new)
- _instance_
- [.data](#LoadingStateMachine+data)
- [.error](#LoadingStateMachine+error)
- [.state](#LoadingStateMachine+state)
- [.asObservable()](#LoadingStateMachine+asObservable) ⇒ <code>Observable<LoadingState></code>
- [.update(newData)](#LoadingStateMachine+update)
- [.start()](#LoadingStateMachine+start)
- [.succeed(data)](#LoadingStateMachine+succeed)
- [.fail(error)](#LoadingStateMachine+fail)
- [.reset()](#LoadingStateMachine+reset)
- [.isNotStarted()](#LoadingStateMachine+isNotStarted) ⇒ <code>Boolean</code>
- [.isLoading()](#LoadingStateMachine+isLoading) ⇒ <code>Boolean</code>
- [.isError()](#LoadingStateMachine+isError) ⇒ <code>Boolean</code>
- [.isSuccess()](#LoadingStateMachine+isSuccess) ⇒ <code>Boolean</code>
- _static_
- [.asError(error)](#LoadingStateMachine.asError) ⇒ <code>LoadingStateMachine<T></code>
- [.asSuccess(data)](#LoadingStateMachine.asSuccess) ⇒ <code>LoadingStateMachine<T></code>
- [.asLoading(data)](#LoadingStateMachine.asLoading) ⇒ <code>LoadingStateMachine<T></code>
<a name="new_LoadingStateMachine_new"></a>
### new LoadingStateMachine()
Handles transitions between different loading state and holds the context data that is related to the current state.
<a name="LoadingStateMachine+data"></a>
### loadingStateMachine.data
Data of the current state. Depending on the current state, this may be undefined.
**Kind**: instance property of [<code>LoadingStateMachine</code>](#LoadingStateMachine)
<a name="LoadingStateMachine+error"></a>
### loadingStateMachine.error
Error of the current state. Depending on the current state, this may be undefined.
**Kind**: instance property of [<code>LoadingStateMachine</code>](#LoadingStateMachine)
<a name="LoadingStateMachine+state"></a>
### loadingStateMachine.state
The current LoadingState
**Kind**: instance property of [<code>LoadingStateMachine</code>](#LoadingStateMachine)
<a name="LoadingStateMachine+asObservable"></a>
### loadingStateMachine.asObservable() ⇒ <code>Observable<LoadingState></code>
Creates a new observable that represents the current state of the machine
**Kind**: instance method of [<code>LoadingStateMachine</code>](#LoadingStateMachine)
**Returns**: <code>Observable<LoadingState></code> - Observable that emits the machine state
<a name="LoadingStateMachine+update"></a>
### loadingStateMachine.update(newData)
Update data while in loading state
**Kind**: instance method of [<code>LoadingStateMachine</code>](#LoadingStateMachine)
| Param | Type |
| ------- | -------------- |
| newData | <code>T</code> |
<a name="LoadingStateMachine+start"></a>
### loadingStateMachine.start()
Starts loading
**Kind**: instance method of [<code>LoadingStateMachine</code>](#LoadingStateMachine)
<a name="LoadingStateMachine+succeed"></a>
### loadingStateMachine.succeed(data)
Transition to success state
**Kind**: instance method of [<code>LoadingStateMachine</code>](#LoadingStateMachine)
| Param | Type |
| ----- | -------------- |
| data | <code>T</code> |
<a name="LoadingStateMachine+fail"></a>
### loadingStateMachine.fail(error)
Transition to error state
**Kind**: instance method of [<code>LoadingStateMachine</code>](#LoadingStateMachine)
| Param | Type |
| ----- | ---------------- |
| error | <code>any</code> |
<a name="LoadingStateMachine+reset"></a>
### loadingStateMachine.reset()
Resets machine to not started
**Kind**: instance method of [<code>LoadingStateMachine</code>](#LoadingStateMachine)
<a name="LoadingStateMachine+isNotStarted"></a>
### loadingStateMachine.isNotStarted() ⇒ <code>Boolean</code>
**Kind**: instance method of [<code>LoadingStateMachine</code>](#LoadingStateMachine)
**Returns**: <code>Boolean</code> - True if machine if loading has not been started or reset
<a name="LoadingStateMachine+isLoading"></a>
### loadingStateMachine.isLoading() ⇒ <code>Boolean</code>
**Kind**: instance method of [<code>LoadingStateMachine</code>](#LoadingStateMachine)
**Returns**: <code>Boolean</code> - True if machine is in loading state
<a name="LoadingStateMachine+isError"></a>
### loadingStateMachine.isError() ⇒ <code>Boolean</code>
**Kind**: instance method of [<code>LoadingStateMachine</code>](#LoadingStateMachine)
**Returns**: <code>Boolean</code> - True if machine is in error state
<a name="LoadingStateMachine+isSuccess"></a>
### loadingStateMachine.isSuccess() ⇒ <code>Boolean</code>
**Kind**: instance method of [<code>LoadingStateMachine</code>](#LoadingStateMachine)
**Returns**: <code>Boolean</code> - True if machine is in success state
<a name="LoadingStateMachine.asError"></a>
### LoadingStateMachine.asError(error) ⇒ <code>LoadingStateMachine<T></code>
Factory to create a new machine in error state
**Kind**: static method of [<code>LoadingStateMachine</code>](#LoadingStateMachine)
**Returns**: <code>LoadingStateMachine<T></code> - The new LoadingStateMachine
| Param | Type |
| ----- | ---------------- |
| error | <code>any</code> |
<a name="LoadingStateMachine.asSuccess"></a>
### LoadingStateMachine.asSuccess(data) ⇒ <code>LoadingStateMachine<T></code>
Factory to create a new machine in success state
**Kind**: static method of [<code>LoadingStateMachine</code>](#LoadingStateMachine)
**Returns**: <code>LoadingStateMachine<T></code> - The new LoadingStateMachine
| Param | Type |
| ----- | -------------- |
| data | <code>T</code> |
<a name="LoadingStateMachine.asLoading"></a>
### LoadingStateMachine.asLoading(data) ⇒ <code>LoadingStateMachine<T></code>
Factory to create a new machine in loading state
**Kind**: static method of [<code>LoadingStateMachine</code>](#LoadingStateMachine)
**Returns**: <code>LoadingStateMachine<T></code> - The new LoadingStateMachine
| Param | Type |
| ----- | ---------------------------------------- |
| data | <code>T</code> \| <code>undefined</code> |
---
<a name="trackLoadingBy"></a>
## trackLoadingBy(loadingStateMachine)
Tracks an observable, by emitting loading events to the passed in state machine
**Kind**: global function
| Param | Type |
| ------------------- | -------------------------------------------- |
| loadingStateMachine | <code>LoadingStateMachine<Data></code> |
<!-- JSDOC END>
<!-- State machine code -> https://stately.ai/viz
import { createMachine, assign } from 'xstate';
const fetchMachine = createMachine<Context>({
id: "LoadingState",
initial: "notStarted",
states: {
notStarted: {
on: {
start: "loading",
},
},
loading: {
on: {
update: "loading",
succeed: "success",
reset: "notStarted",
fail: "error",
},
},
success: {
on: {
reset: "notStarted",
start: "loading",
},
},
error: {
on: {
reset: "notStarted",
start: "loading",
},
},
},
});
```
-->