diff --git a/libs/shared/ngrx-callstate-store/README.md b/libs/shared/ngrx-callstate-store/README.md
index 9e43d86..a6da01f 100644
--- a/libs/shared/ngrx-callstate-store/README.md
+++ b/libs/shared/ngrx-callstate-store/README.md
@@ -1,7 +1,222 @@
-
NgRx Callstate ComponentStore
+# 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.
-## 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(
+ 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);
+```
diff --git a/libs/shared/ngrx-callstate-store/package.json b/libs/shared/ngrx-callstate-store/package.json
index 11447ee..1d9a2e3 100644
--- a/libs/shared/ngrx-callstate-store/package.json
+++ b/libs/shared/ngrx-callstate-store/package.json
@@ -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"
diff --git a/libs/shared/ngrx-callstate-store/src/index.ts b/libs/shared/ngrx-callstate-store/src/index.ts
index 51c5b26..1de3f04 100644
--- a/libs/shared/ngrx-callstate-store/src/index.ts
+++ b/libs/shared/ngrx-callstate-store/src/index.ts
@@ -1,2 +1,2 @@
-export * from './lib/custom-component-store';
+export * from './lib/call-state-component-store';
export * from './lib/external.model';
diff --git a/libs/shared/ngrx-callstate-store/src/lib/custom-component-store.ts b/libs/shared/ngrx-callstate-store/src/lib/call-state-component-store.ts
similarity index 92%
rename from libs/shared/ngrx-callstate-store/src/lib/custom-component-store.ts
rename to libs/shared/ngrx-callstate-store/src/lib/call-state-component-store.ts
index 7e0a884..08c2259 100644
--- a/libs/shared/ngrx-callstate-store/src/lib/custom-component-store.ts
+++ b/libs/shared/ngrx-callstate-store/src/lib/call-state-component-store.ts
@@ -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 {
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 = this.select(
(state) => state.callState === 'LOADING'
- ).pipe(switchMap((loading) => nonFlickerLoader(of(loading))));
+ ).pipe(
+ switchMap((loading) => nonFlickerLoader(of(loading), this.flickerTime))
+ );
readonly isLoaded$: Observable = this.select(
(state) => state.callState === 'LOADED'
diff --git a/libs/shared/ngrx-callstate-store/src/lib/call-state.model.ts b/libs/shared/ngrx-callstate-store/src/lib/call-state.model.ts
index 7dca57b..bcd13b2 100644
--- a/libs/shared/ngrx-callstate-store/src/lib/call-state.model.ts
+++ b/libs/shared/ngrx-callstate-store/src/lib/call-state.model.ts
@@ -29,10 +29,6 @@ export class CallStateErrorHandler implements ErrorHandler {
};
}
-export interface EsuiteError {
- code: string;
-}
-
export interface ErrorState {
error: CustomError;
}
diff --git a/libs/shared/ngrx-callstate-store/src/lib/external.model.ts b/libs/shared/ngrx-callstate-store/src/lib/external.model.ts
index 49be42e..1d1ff49 100644
--- a/libs/shared/ngrx-callstate-store/src/lib/external.model.ts
+++ b/libs/shared/ngrx-callstate-store/src/lib/external.model.ts
@@ -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 {
@@ -16,3 +21,12 @@ export const ERROR_TOKEN = new InjectionToken>('error', {
export const provideErrorHandler = >(
errorHandlerClass: Type
): ClassProvider => ({ provide: ERROR_TOKEN, useClass: errorHandlerClass });
+
+export const FLICKER_TIME = new InjectionToken('flicker', {
+ factory: () => 300,
+});
+
+export const provideFlickerDelay = (delay: number): ValueProvider => ({
+ provide: FLICKER_TIME,
+ useValue: delay,
+});