# 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( 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): 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): 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): 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 { 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 { 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 { 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); ```