mirror of
https://github.com/Raghu-Ch/angular-challenges.git
synced 2026-02-10 04:43:03 -05:00
feat(ngrxcallstatelibray): update readme
This commit is contained in:
@@ -1,7 +1,222 @@
|
||||
<p align='center'>NgRx Callstate ComponentStore</p>
|
||||
# NgRx CallState ComponentStore
|
||||
|
||||
<br>
|
||||
NgRx CallState ComponentStore is a small library that extends the **@Ngrx/component-store** by adding a loading and error state to your custom state.
|
||||
|
||||
## Intro
|
||||
## Installation
|
||||
|
||||
Small library to enhance ComponentStore for have a loading or error state for all XHR request.
|
||||
Requires @Ngrx/component-store
|
||||
|
||||
Yarn:
|
||||
|
||||
```bash
|
||||
yarn add @tomalaforge/ngrx-callstate-store
|
||||
```
|
||||
|
||||
NPM:
|
||||
|
||||
```bash
|
||||
npm i @tomalaforge/ngrx-callstate-store
|
||||
```
|
||||
|
||||
## Introduction
|
||||
|
||||
When making XHR calls or any asynschronous tasks, you always need a loading or error state. By using `CallStateComponentStore`, you can easily manage the loading and error states of your async tasks, which makes your code more organized and maintainable.
|
||||
|
||||
## Example
|
||||
|
||||
```typescript
|
||||
@Injectable()
|
||||
export class AppStore extends CallStateComponentStore<{todos: Todo[]}> {
|
||||
readonly todos$ = this.select((state) => state.todos);
|
||||
|
||||
readonly vm$ = this.select({
|
||||
todos: this.todos$,
|
||||
loading: this.loading$,
|
||||
}, {debounce: true});
|
||||
|
||||
constructor(private todoService: TodoService) {
|
||||
super({ todos: [] });
|
||||
}
|
||||
|
||||
readonly fetchTodo = this.effect<void>(
|
||||
pipe(
|
||||
tap(() => this.startLoading()),
|
||||
switchMap(() =>
|
||||
this.todoService.getAllTodo().pipe(
|
||||
tapResponse(
|
||||
(todos) => this.stopLoading({ todos }),
|
||||
(error: unknown) => this.handleError(error, {todos: []})
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
By extending your class with `CallStateComponentStore`, a `CallState` property is added to your state.
|
||||
|
||||
> You don't need to provide a state if you only want to use the `CallState` property.
|
||||
|
||||
```typescript
|
||||
export type LoadingState = 'INIT' | 'LOADING' | 'LOADED';
|
||||
|
||||
export interface ErrorState {
|
||||
error: CustomError;
|
||||
}
|
||||
|
||||
export type CallState = LoadingState | ErrorState;
|
||||
```
|
||||
|
||||
> You can override [`CustomError`](#errorstate) as needed.
|
||||
|
||||
## API
|
||||
|
||||
### updater
|
||||
|
||||
##### startLoading
|
||||
|
||||
The `startLoading` method sets the `CallState` property to the `LOADING` state. You can pass optional state properties if you want to patch your own state.
|
||||
|
||||
```typescript
|
||||
startLoading = (state: Optional<T>): void
|
||||
```
|
||||
|
||||
##### stopLoading
|
||||
|
||||
The `stopLoading` method sets the `CallState` to the `LOADED` state. You can pass an optional state properties as well.
|
||||
|
||||
```typescript
|
||||
stopLoading = (state: Optional<T>): void
|
||||
```
|
||||
|
||||
##### updateCallState
|
||||
|
||||
The `updateCallState` method updates the callState with the inputed value.
|
||||
|
||||
```typescript
|
||||
updateCallState = (callState: CallState): void
|
||||
```
|
||||
|
||||
##### handleError
|
||||
|
||||
The `handleError` method handles errors. You can pass an optional state.
|
||||
|
||||
```typescript
|
||||
handleError = (error: unknown, state: Optional<T>): void
|
||||
```
|
||||
|
||||
### selector
|
||||
|
||||
##### isLoading$
|
||||
|
||||
`isLoading$` return a boolean, true if state is loading, false otherwise
|
||||
|
||||
##### isLoadingWithFlicker$
|
||||
|
||||
`isLoadingWithFlicker$` return the same as `isLoading$` but with a small delay. This can be useful when you don't want your page to flicker.
|
||||
|
||||
##### isLoaded$
|
||||
|
||||
`isLoaded$` return a boolean, true if state is loaded, false otherwise
|
||||
|
||||
##### callState$
|
||||
|
||||
`isLoading$` return the `CallState`
|
||||
|
||||
##### error$
|
||||
|
||||
`isLoading$` return your error message using the `getErrorMessage` of your `ErrorState`. [(see below)](#errorstate)
|
||||
|
||||
---
|
||||
|
||||
### Customize the library
|
||||
|
||||
##### ErrorState
|
||||
|
||||
You can provide your own implementation of The `ErrorState` by implementing `ErrorHandler`
|
||||
|
||||
```typescript
|
||||
export interface ErrorHandler<T extends CustomError> {
|
||||
toError: (error: unknown) => T;
|
||||
getErrorMessage: (error?: T) => string | undefined;
|
||||
}
|
||||
```
|
||||
|
||||
The `toError` method converts the error input into the desired error object.
|
||||
The `getErrorMessage` method returns the well-formed error message that you want to display to your user.
|
||||
|
||||
The current implementation is as follow:
|
||||
|
||||
```typescript
|
||||
export const UNKNOWN_ERROR_CAUSE = 'UNKNOWN_ERROR';
|
||||
export const UNKNOWN_ERROR_MESSAGE = 'unknown error occured';
|
||||
|
||||
export class CallStateError {
|
||||
name: string;
|
||||
message: string;
|
||||
stack?: string;
|
||||
|
||||
constructor(name = '', message = '') {
|
||||
this.name = name;
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
|
||||
export class CallStateErrorHandler implements ErrorHandler<CallStateError> {
|
||||
toError = (error: unknown): CallStateError => {
|
||||
if (error instanceof CallStateError) return error;
|
||||
if (error instanceof Error)
|
||||
return new CallStateError(error.name, error.message);
|
||||
return new CallStateError(UNKNOWN_ERROR_CAUSE, UNKNOWN_ERROR_MESSAGE);
|
||||
};
|
||||
|
||||
getErrorMessage = (error?: CallStateError): string | undefined => {
|
||||
return error?.message;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Let's say you want to customize it as follow:
|
||||
|
||||
```typescript
|
||||
export const UNKNOWN_ERROR_MESSAGE = 'unknown error occured';
|
||||
|
||||
export class MyError {
|
||||
message: string;
|
||||
code: number
|
||||
|
||||
constructor(message = '', code = 404) {
|
||||
this.message = message;
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
|
||||
export class MyErrorHandler implements ErrorHandler<MyError> {
|
||||
toError = (error: unknown): MyError => {
|
||||
if (error instanceof MyError) return error;
|
||||
if (error instanceof Error)
|
||||
return new MyError(error.message);
|
||||
return new MyError(UNKNOWN_ERROR_MESSAGE);
|
||||
};
|
||||
|
||||
getErrorMessage = (error?: MyError): string | undefined => {
|
||||
return error.code error?.message;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Now to override the default implementation, you need to provide it as follow :
|
||||
|
||||
```typescript
|
||||
provideErrorHandler(MyErrorHandler);
|
||||
```
|
||||
|
||||
> You can provide it at root level to apply it to your whole application or at the component level for more specific implementation.
|
||||
|
||||
##### Flicker Delay
|
||||
|
||||
The default delay is 300ms but you can override it by providing it as follow:
|
||||
|
||||
```typescript
|
||||
provideFlickerDelay(500);
|
||||
```
|
||||
|
||||
@@ -1,6 +1,22 @@
|
||||
{
|
||||
"name": "@tomalaforge/ngrx-callstate-store",
|
||||
"version": "0.0.1",
|
||||
"version": "0.0.3",
|
||||
"description": "Enhance NgRx component-store by providing a loading/error state",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/tomalaforge/angular-challenges/tree/main/libs/shared/ngrx-callstate-store"
|
||||
},
|
||||
"keywords": [
|
||||
"angular",
|
||||
"state",
|
||||
"ngrx/component-store"
|
||||
],
|
||||
"author": {
|
||||
"name": "Thomas Laforge"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/common": "^15.0.0",
|
||||
"@angular/core": "^15.0.0"
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export * from './lib/custom-component-store';
|
||||
export * from './lib/call-state-component-store';
|
||||
export * from './lib/external.model';
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
CallStateError,
|
||||
getErrorCallState,
|
||||
} from './call-state.model';
|
||||
import { ERROR_TOKEN } from './external.model';
|
||||
import { ERROR_TOKEN, FLICKER_TIME } from './external.model';
|
||||
import { nonFlickerLoader } from './non-flicker-loader';
|
||||
|
||||
export const INITIAL_TOKEN = new InjectionToken('initial data');
|
||||
@@ -26,6 +26,7 @@ export class CallStateComponentStore<
|
||||
: U & CallStateComponentState
|
||||
> extends ComponentStore<T> {
|
||||
private error = inject(ERROR_TOKEN);
|
||||
private flickerTime = inject(FLICKER_TIME);
|
||||
|
||||
constructor(@Inject(INITIAL_TOKEN) initialState: U) {
|
||||
super({ callState: 'INIT', ...initialState } as T);
|
||||
@@ -37,7 +38,9 @@ export class CallStateComponentStore<
|
||||
|
||||
readonly isLoadingWithFlicker$: Observable<boolean> = this.select(
|
||||
(state) => state.callState === 'LOADING'
|
||||
).pipe(switchMap((loading) => nonFlickerLoader(of(loading))));
|
||||
).pipe(
|
||||
switchMap((loading) => nonFlickerLoader(of(loading), this.flickerTime))
|
||||
);
|
||||
|
||||
readonly isLoaded$: Observable<boolean> = this.select(
|
||||
(state) => state.callState === 'LOADED'
|
||||
@@ -29,10 +29,6 @@ export class CallStateErrorHandler implements ErrorHandler<CallStateError> {
|
||||
};
|
||||
}
|
||||
|
||||
export interface EsuiteError {
|
||||
code: string;
|
||||
}
|
||||
|
||||
export interface ErrorState {
|
||||
error: CustomError;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
/* eslint-disable @typescript-eslint/no-empty-interface */
|
||||
import { ClassProvider, InjectionToken, Type } from '@angular/core';
|
||||
import {
|
||||
ClassProvider,
|
||||
InjectionToken,
|
||||
Type,
|
||||
ValueProvider,
|
||||
} from '@angular/core';
|
||||
import { CallStateErrorHandler } from './call-state.model';
|
||||
|
||||
export interface ErrorHandler<T extends CustomError> {
|
||||
@@ -16,3 +21,12 @@ export const ERROR_TOKEN = new InjectionToken<ErrorHandler<any>>('error', {
|
||||
export const provideErrorHandler = <T extends ErrorHandler<any>>(
|
||||
errorHandlerClass: Type<T>
|
||||
): ClassProvider => ({ provide: ERROR_TOKEN, useClass: errorHandlerClass });
|
||||
|
||||
export const FLICKER_TIME = new InjectionToken<number>('flicker', {
|
||||
factory: () => 300,
|
||||
});
|
||||
|
||||
export const provideFlickerDelay = (delay: number): ValueProvider => ({
|
||||
provide: FLICKER_TIME,
|
||||
useValue: delay,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user