mirror of
https://github.com/Raghu-Ch/angular-challenges.git
synced 2026-02-11 05:13:02 -05:00
232 lines
5.6 KiB
Markdown
232 lines
5.6 KiB
Markdown
# NgRx CallState ComponentStore
|
|
|
|
NgRx CallState ComponentStore is a small library that extends the **@Ngrx/component-store** by adding a loading and error state to your custom state.
|
|
|
|
## Installation
|
|
|
|
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
|
|
|
|
### initialization
|
|
|
|
##### setInitState
|
|
|
|
The `setInitState` method lets you initialize your custom state if you are not using the constructor.
|
|
|
|
```typescript
|
|
setInitState = (state: T): void
|
|
```
|
|
|
|
### 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);
|
|
```
|