Removing boilerplate code in Ngrx component store
Tags: angular, typescript, ngrx, javascript
Published: 2022-03-13 21:59:00

## Introduction
Ngrx component store is a great package for state management at component level in angular. For small applications and applications having isolated component trees, which require their own state and that do not need to be shared, it is a great fit. It comes with the power of a push based mechanism but at the level of service. In this article, I will assume you have basic understanding about the [@ngrx/component-store](https://ngrx.io/guide/component-store). So, we will not focus on discussing the basics of component store. Instead, we will talk about removing duplicate code while using the component store. We will write a lot of code. So, let’s get started.

## App structure

Below is the structure of our demo application:-
```
├── src/
│   ├── app/
│   │    ├── albums/
│   │    │     ├── albums.component.ts
│   │    │     ├── albums.component.html
│   │    │     ├── albums.component.css
│   │    │     ├── albums.store.ts
│   │    ├── users/
│   │    │     ├── users.component.ts
│   │    │     ├── users.component.html
│   │    │     ├── users.component.css
│   │    │     ├── users.store.ts
│   │    │── app.component.html
│   │    │── app.component.css
│   │    │── app.component.ts
│   │    │── app.module.ts
│   │    │── base-component.store.ts
│   │    │── count.component.ts
│   │
│   ├── assets/
│   ├── environments/
│   ├── favicon.ico
│   ├── index.html
│   ├── main.ts
│   ├── polyfills.ts
│   ├── styles.css
│   └── test.ts
├── .browserslistrc
├── karma.conf.js
├── tsconfig.json
```
We have two components:- `Users` and `Albums`. Both have their own component stores. We also have one **_base component store_**. We will talk about it later in the article. Also we have one `count` compnent to show the total number of items loaded.
Let’s start first with creating a generic state interface.

## Generic State interface
This interface represents the state that each component store will have whenever we create a new state. Below is the code snippet for this,
```typescript
export type LOADING = 'IDLE' | 'LOADING';
type CallState = LOADING | HttpErrorResponse;

export interface GenericState<T> {
  data: T;
  callState: CallState;
  totalCount: number;
}
```
The `GenericState` interface accepts a generic type `<T>` which represents the structure of our data property. Out `data` can either be a collection of itmes or a single item. Then we have `callState` which will be either of type `LOADING` or `HttpErrorResponse`. We can also create them separately as `loading` and `error`. But I would prefer to have them in a single property. Then we have the `totalCount` which will be the length of total items if our data is a collection of items, otherwise(if data is an object or single item) we can set it to 0 in order to ignore it.

## BaseComponentStore
Now let's move to the next step and create a `BaseComponentStore` which will be extended by `albums` and `users` component store. The basic idea behind creating this is to provide boilerplate code for our both stores.
```typescript
@Injectable()
export class BaseComponentStore<
  T extends GenericState<unknown>
> extends ComponentStore<T> {
  baseSelector = this.select(({ callState, totalCount }) => ({
    totalCount,
    loading: callState === 'LOADING',
    error: callState instanceof HttpErrorResponse ? callState : undefined,
  }));
  protected updateError = this.updater((state, error: CallState) => ({
    ...state,
    callState: error,
  }));

  protected setLoading = this.updater((state) => ({
    ...state,
    data: undefined,
    callState: 'LOADING',
  }));

  protected updateSuccess = this.updater((state, data: T['data']) => ({
    ...state,
    data,
    totalCount: Array.isArray(data) ? data.length : 0,
    callState: 'IDLE',
  }));
}
```
Our `BaseComponentStore` accepts the generic type `T` which by default extends the `GenericState` of `unknown`. We are using `unknown` here because we don't’ know the type of data here. But we are sure about the types of other properties such as `callState` and `totalCount`. This `BaseComponentStore` extends the `ComponentStore` in order to have access to state and other methods.
Next we are creating the `baseSelector`. This will be used to get all other properties required by the component. Error, loading and totalCount are common properties that are required by the components. So, it's a good idea to have them in the base selector. We can add more properties to this selector based on our requirement.

Next we have the `updateError` method. Again, most of the time the errors are handled in the common way. So, we can have this method in our base component store.
Similarly, we have `setLoading` method to update the loading state.
Then we have `updateSuccess` method to update the data in the component store. We are assuming here that the data is simply the new list or new item. So it's easy to set. So we are updating the `data`, setting up the `totalCount` with the length of items and updating the `callState` back to `IDLE`.

Now with this we have our boilerplate/duplicate code inside the `BaseComponentStore` which gives benefit to all other stores which will extend it.

## Implementing AlbumsStore
Now that our base component store is ready, let’s start with creating an `AlbumsStore` which we will use in `AlbumsComponent`.
Let’s first create required interfaces,
```typescript
interface Album {
  id: number;
  userId: number;
  title: string;
}

interface AlbumViewModel {
  albums: Album[];
  loading: boolean;
  totalCount: number;
  error: HttpErrorResponse;
}
```
We have the `Album` interface which has id, userId and title properties. Then we are creating a `AlbumViewModel` interface which is used to build the `viewModel`. A `viewModel` is the pattern used to expose the single observable instead of multiple observables which will be used by the component.

Let’s move to the next step of creating `AlbumsStore`.
```typescript
@Injectable()
export class AlbumsStore extends BaseComponentStore<GenericState<Album[]>> {
  readonly albums$ = this.select((state) => state.data);
  readonly vm$: Observable<AlbumViewModel> = this.select(
    this.baseSelector,
    this.albums$,
    (state, albums) => ({ ...state, albums })
  );
  constructor(private readonly http: HttpClient) {
    super({
      data: [],
      callState: 'IDLE',
      totalCount: 0,
    });
  }

  readonly getAlbums = this.effect((params$: Observable<unknown>) => {
    return params$.pipe(
      tap((_) => this.setLoading()),
      switchMap((_) =>
        this.http
          .get<Album[]>('https://jsonplaceholder.typicode.com/albums')
          .pipe(
            tapResponse(
              (users: Album[]) => this.updateSuccess(users),
              (error: HttpErrorResponse) => this.updateError(error)
            )
          )
      )
    );
  });
}
```
Our `AlbumsStore` extends the `BaseComponentStore` by providing the `Album[]` as a type for the `GenericState<T>`. Now the first thing we can notice here is that we are not creating a new interface for the state(something like `AlbumsState`). All the common properties, `totalCount` and `callState` are always there in the `BaseComponentStore` via 	`GenericState`. So we don’t need that. Next we are creating `albums$`. This is just a mapping of `data` to the `albums`. In our component, instead of data, using the album as property might be the better naming convention.

After that we have our `vm$` which is the single observable exposing multiple properties. Now again we can notice the benefit of creating the boilerplate code. We are not adding the `loading`, `error` and `totaCount` here since they will be always coming from `baseSelector`.

Now we have our selectors done, let’s start with initializing the state. We are initializing it via calling the parent constructor(as per component store convention) with our default state.

Next we have the effect which will fetch the albums from the server. Notice that we are using the `setLoading` method from our `BaseComponentStore` to update the `callState` to `LOADING`. This will be used in the component to show the loader. Similarly, we are also using the `updateSuccess` and `updateError` to set the data and error in the state. Also, ngrx component store provides `tapResponse` operator to gracefully handle the errors. So we are using it.

## Using AlbumsStore in the component
We are ready to use the `AlbumStore` inside our `AlbumsComponent`. Let’s have a look into `album.component.ts`,
```typescript
@Component({
  selector: 'app-albums',
  templateUrl: './albums.component.html',
  styleUrls: ['./albums.component.css'],
  providers: [AlbumsStore],
})
export class AlbumsComponent implements OnInit {
  vm$ = this.store.vm$;
  constructor(private store: AlbumsStore) {}

  ngOnInit() {
    this.store.getAlbums({});
  }

  fetch() {
    this.store.getAlbums({});
  }
}
```
Our `AlbumsComponent` is simple. It has vm$ observable as property. We are calling our effect on `ngOnInit` to fetch the albums. After this we have one method `fetch` which we can call whenever we want to re-fetch our data.

Let's look at the `album.component.html` file as well.
```html
<ng-container *ngIf="vm$ | async as vm">
  <button (click)="fetch()">Fetch Albums</button>
  <ng-container *ngIf="!vm.loading; else loading">
    <count [count]="vm.totalCount"></count>
    <ng-container *ngFor="let album of vm.albums">
      <pre>ID: {{ album.id }}</pre>
      <pre>UserId: {{ album.userId }}</pre>
      <pre>title: {{ album.title }}</pre>
    </ng-container>
  </ng-container>
</ng-container>
<ng-template #loading>
  <div>Loading...</div>
</ng-template>
```
In the html file, we are using an async pipe to subscribe for the `vm$` property. `async` pipe will automatically update our view whenever our `vm$` changes. We have a button(`Fetch Albums`) to re-fetch the albums. Then we are showing the albums if we have the `albums` available otherwise we are showing the loading text. We are also showing the count of the albums via `count` component and then showing the album info via `*ngFor`. `<count>` is a simple component which accepts count as `@Input` and then showing them with message `Total count: {{count}}`,
```typescript
@Component({
  selector: 'count',
  template: `<h1>Total Count: {{count}}!</h1>`,
  styles: [`h1 { font-family: Lato; }`],
})
export class CountComponent {
  @Input() count: number;
}
```
## Implementing UsersStore
Next we can have the `UsersStore` and `UsersComponent`. The code snippet is identical to the album's feature. I am just adding the snippet for the `UsersStore` and the rest of the code can be seen at [stackblitz](https://stackblitz.com/edit/angular-ivy-rgps6q?file=src%2Fapp%2Fusers%2Fusers.component.ts).
```typescript
interface User {
  id: number;
  name: string;
  username: string;
}

interface UserViewModel {
  users: User[];
  loading: boolean;
  totalCount: number;
  error: HttpErrorResponse;
}
```
```typescript
@Injectable()
export class UsersStore extends BaseComponentStore<GenericState<User[]>> {
  readonly users$ = this.select((state) => state.data);
  readonly vm$: Observable<UserViewModel> = this.select(
    this.baseSelector,
    this.users$,
    (state, users) => ({ ...state, users })
  );
  constructor(private readonly http: HttpClient) {
    super({
      data: [],
      callState: 'IDLE',
      totalCount: 0,
    });
  }

  readonly getUsers = this.effect((params$: Observable<unknown>) => {
    return params$.pipe(
      tap((_) => this.setLoading()),
      switchMap((_) =>
        this.http
          .get<User[]>('https://jsonplaceholder.typicode.com/users')
          .pipe(
            delay(300),
            tapResponse(
              (users: User[]) => this.updateSuccess(users),
              (error: HttpErrorResponse) => this.updateError(error)
            )
          )
      )
    );
  });
}
```
Everything is identical to  the `AlbumsStore`. Instead of albums we have users here. Below is the gif of the working example,

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m6j1ycz7vk6t97mcn8yk.gif)

With the help of our `BaseComponentStore`, we are able to remove a lot of duplicate code. Hence we need to write less code every time we create new component store and we will still get the same results.

Full code example can be found on below stackblitz link:-
https://stackblitz.com/edit/angular-ivy-rgps6q?file=src%2Fapp%2Fbase-component.store.ts