@jscutlery/operators v3.2.5
@jscutlery/operators
This package regroups a couple of RxJS operators meant to simplify some common patterns.
Installation
yarn add @jscutlery/operators
# or
npm install @jscutlery/operatorssuspensify
When dealing with an asynchronous source of data in a web application, it is a common requirement to display different content depending on the following states:
- pending: the data is being fetched
- error: an error occurred while fetching the data
- success: some data has been fetched
- finalized: there is no more data to fetch or an error happened (it is called
finalizedand notcompletedto avoid confusion with RxJScompleteevent which only happens if there is no error).
The most common ways of implementing this are error-prone as they are either based on a complex combination of native RxJS operators or side effects that break the reactivity chain.
The suspensify operator is meant to provide a simple and efficient way of dealing with data fetching by providing deriving a state from the source observable.
Goals
- ⚡️ simplify the implementation of asynchronous data fetching
- 🎬 know when the data is being fetched and show some loading indicator
- 🐞 avoid common mistakes like showing data and the last error simultaneously
- 💥 simplify and encourage error-handling
Usage
Strict mode (default)
Thanks to strict mode the emitted suspense can be narrowed down.
interval(1000)
.pipe(take(2), suspensify())
.subscribe((suspense) => {
suspense.value; // 💥
suspense.error; // 💥
if (suspense.hasValue) {
suspense.value; // ✅
suspense.error; // 💥
}
if (suspense.hasError) {
suspense.value; // 💥
suspense.error; // ✅
}
});{
finalized: false,
hasError: false,
hasValue: false,
pending: true,
}
{
finalized: false,
hasError: false,
hasValue: false,
pending: false,
value: 0,
}
...
{
finalized: true,
hasError: false,
hasValue: true,
pending: false,
value: 1,
}Lax mode
interval(1000)
.pipe(take(2), suspensify({strict: false}))
.subscribe((data) => console.log(data));{
finalized: false,
hasError: false,
hasValue: false,
pending: true,
value: undefined,
error: undefined,
}
{
finalized: false,
hasError: false,
hasValue: false,
pending: false,
value: 0,
error: undefined,
}
...
{
finalized: true,
hasError: false,
hasValue: true,
pending: false,
value: 1,
error: undefined,
}With Angular
@Component({
template: `
<ng-container *ngIf="suspense$ | async as suspense">
<my-spinner *ngIf="suspense.pending"></my-spinner>
<div *ngIf="suspense.hasError">
{{ suspense.error }} // ✅
{{ suspense.value }} // 💥 will not compile in strict mode
</div>
<div *ngIf="suspense.hasValue">
{{ suspense.error }} // 💥 will not compile in strict mode
{{ suspense.value }} // ✅
</div>
</ng-container>
`,
})
export class MyComponent {
suspense$ = this.fetchData().pipe(suspensify());
...
}With @rx-angular/state
@Component({
template: `
<my-spinner *ngIf="pending$ | async"></my-spinner>
<div *ngIf="error$ | async as error">{{ error }}</div>
<div *ngIf="value$ | async" as value>{{ value }}</div>
`,
})
export class MyComponent {
value$ = this.state.select('beer', 'value');
error$ = this.state.select('beer', 'error');
pending$ = this.state.select('beer', 'pending');
constructor(private state: RxState<{ beer: Suspense<'🍻'> }>) {
this.state.connect('beer', this.fetchBeer());
}
}Alternatives
F.A.Q.
How does it defer from materialize?
materialize doesn't produce a derived state. In fact, the C (complete) event doesn't contain the last emitted value.
Also, materialized doesn't trigger a pending event so the observable doesn't emit anything before the first value is emitted or an error occurs or the source completes.
Roadmap
mergeSuspensefunction should merge multiple sources in one state that contains the global state of all sources and each one of them.
1 year ago
11 months ago
1 year ago
7 months ago
8 months ago
9 months ago
8 months ago
6 months ago
6 months ago
6 months ago
9 months ago
10 months ago
10 months ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago