Reusable component store for pagination using generics
Tags: angular, ngrx, tutorial, javascript
Published: 2023-03-20 06:09:00

In [the last article](https://dev.to/this-is-angular/handling-pagination-with-ngrx-component-stores-1j1p) we created a [component store](https://ngrx.io/guide/component-store) using NgRx to handle the pagination of todo items

Unfortunately, this approach is very specific to our domain and it is now time for us to look for a reusable solution that we can slap on another entity that we would like to paginate the same way

---

## Table of content

- [Creating our new store](#creating-our-new-store)
  - [The state](#the-state)
  - [The store itself](#the-store-itself)
    - [Setting the foundations](#setting-the-foundations)
    - [Creating selectors](#creating-selectors)
    - [Handling pagination](#handling-pagination)
  - [Using NgRx lifecycle hooks](#using-ngrx-lifecycle-hooks)
- [Introducing generics](#introducing-generics)
  - [Rework our state](#rework-our-state)
  - [Updating our store](#updating-our-store)
  - [Updating our state]()


---

## Creating our new store

Let's begin by creating a new `paginated-items.component-store.ts` file where our 
pagination logic will take place.

### The state

Before defining our store, we will first have to focus on its state

Here is our state in its current form:

```ts
export interface AppState {
  todoItems: TodoItem[];
  offset: number;
  pageSize: number;
}
```

We can divide its properties into two main categories:

- Everything related to the pagination (`offset`, `pageSize`)
- Everything related to the items themselves (only `todoItems` in our case)

From this, we can extract a subsequent interface, which has the responsibility of wrapping the parameters related to the pagination:

```ts
export interface PaginationDetails {
  offset: number;
  pageSize: number;
}
```

And another one which will wrap the content of the page:

```ts
export interface PageContent {
  todoItems: TodoItem[];
}
```

Using these two interfaces, we now have a state that is a bit more explicit:

```ts
export interface PaginatedItemsState {
  paginationDetails: PaginationDetails;
  pageContent: PageContent;
}
```

Great, let's move on!

### The store itself

#### Setting the foundations

This store will be defined as an abstract class so that each subsequent store can add its own logic to is.

Using the previously defined state, we can write this:

```ts
@Injectable()
// 👇 Beware not to forget the `abstract` here
export abstract class PaginatedItemsComponentStore
  extends ComponentStore<PaginatedItemsState> { }
```

#### Creating selectors

Having state management is great, being able to access its content is better: let's add some selectors.

We can start by adding some base ones, to retrieve each property:

```ts
@Injectable()
export abstract class PaginatedItemsComponentStore
  extends ComponentStore<PaginatedItemsState>
{
  readonly selectPaginatedItemsState = this.select((state) => state);

  readonly selectPaginationDetails = this.select(
    this.selectPaginatedItemsState,
    ({ paginationDetails }) => paginationDetails
  );

  readonly selectOffset = this.select(
    this.selectPaginationDetails,
    ({ offset }) => offset
  );

  readonly selectPageSize = this.select(
    this.selectPaginationDetails,
    ({ pageSize }) => pageSize
  );

  readonly selectPageContent = this.select(
    this.selectPaginatedItemsState,
    ({ pageContent }) => pageContent
  );

  readonly selectTodoItems = this.select(
    this.selectPageContent,
    ({ todoItems }) => todoItems
  );
}
```

> That's a lot of selectors but keep in mind that the purpose of this store is to be flexible enough so that any store that inherits from it can use those internals instead of rewriting them somewhere else

#### Handling pagination

From our store, let's add some updaters to update our state:

```ts
@Injectable()
export abstract class PaginatedItemsComponentStore
  extends ComponentStore<PaginatedItemsState>
{
  /* Selectors omitted here */

  private readonly updatePagination = this.updater(
    (state, paginationDetails: PaginationDetails) => ({ ...state, paginationDetails })
  );

  private readonly updatePaginatedItems = this.updater(
    (state, pageContent: PageContent) => ({ ...state, pageContent })
  );
}
```

> I will be using one for the pagination and one for the paginated items but feel free to be more or less granular if you want to!

Finally, we can copy, paste and adapt a bit our two previous `loadPage` and `loadNextPage` effects from our `AppComponentStore`:

```ts
@Injectable()
export abstract class PaginatedItemsComponentStore
  extends ComponentStore<PaginatedItemsState>
{
  /* Selectors omitted here */

  // 👇 Don't forget to also inject our service
  private readonly _todoItemService = inject(TodoItemService);

  readonly loadPage = this.effect((trigger$: Observable<void>) => {
    return trigger$.pipe(
      // 👇 We can directly access our pagination details from our selector
      withLatestFrom(this.selectPaginationDetails),
      switchMap(([, { offset, pageSize }]) =>
        this._todoItemService.getTodoItems(offset, pageSize).pipe(
          tapResponse(
            (todoItems: TodoItem[]) => this.updatePaginatedItems({ todoItems }),
            () => console.error("Something went wrong")
          )
        )
      )
    );
  });

  readonly loadNextPage = this.effect((trigger$: Observable<void>) => {
    return trigger$.pipe(
      // 👇 Same here
      withLatestFrom(this.selectPaginationDetails),
      tap(([, { offset, pageSize }]) => this.updatePagination({
        offset: offset + pageSize,
        pageSize
      })),
      tap(() => this.loadPage())
    );
  });

  /* Updaters omitted here */
}
```

### Using NgRx lifecycle hooks

As we did in our first version, we can also take advantage of the `OnStoreInit` lifecycle hook here to load the page on startup:

```ts
@Injectable()
export abstract class PaginatedItemsComponentStore
  extends ComponentStore<PaginatedItemsState>
  implements OnStoreInit
{
  ngrxOnStoreInit() {
    this.loadPage();
  }
}
```

By doing so, we can ensure that each store paginating items, and thus inheriting this one, will load the first page upon creation

### Inheriting from our store

Finally, all that is left to do is for our `AppComponentStore` to inherit from our `PaginatedItemsComponentStore` instead of `ComponentStore<AppState>`

For that, we need to change our initial state and get rid of our own `AppState` to use the provided `PaginatedItemsState`:

```ts
// app.component-store.ts
const initialState: PaginatedItemsState = {
  paginationDetails: {
    offset: 0,
    pageSize: 10,
  },
  pageContent: {
    todoItems: [],
  },
};
```

Once this is done, we can delete all updaters, effects and lifecycle hooks implementations from our component store and use instead the logic of the `PaginatedComponentStore`:

```ts
@Injectable()
export class AppComponentStore
  extends PaginatedItemsComponentStore
{
  readonly vm$ = this.select(
    this.selectTodoItems,
    (todoItems) => ({ todoItems }));

  constructor() {
    super(initialState);
  }
}
```

If you run your application after this change, everything should still be working as it was before, yay!

## Introducing generics

So far so good but let's not rejoice so fast

If everything as been as easy as copying and pasting code from our former store to the new one, it is only because we are constraining it to `TodoItem`s

In a regular application, you may (surely) have more than one type of entity to paginate

Fortunately, TypeScript handles [generics](https://www.typescriptlang.org/docs/handbook/2/generics.html) pretty well and this is something we can leverage to increase the reusability of our `PaginatedItemsComponentStore`

### Rework our state

The first place to look at is our `PageContent` interface

In this one, we are defining the content as an array of `TodoItem` but we want to allow for a broader set of types

We may be tempted to change it to `any[]` but since we are using _Type_Script and not _Any_Script, we may find a better way

Using generics, we can specify to our interface that we would like to have an array of items whose type will be `TItem` since we don't know it yet:

```ts
export interface PageContent<TItem> {
  // 👇 Since we are manipulating items now I renamed the property
  items: TItem[];
}
```

> By convention, I'm naming any generic type by starting with a `T` followed by its logical meaning

Updating this interface will need us to rewrite the `PaginatedItemsState` as well since we need to propagate the generics:

```ts
export interface PaginatedItemsState<TItem> {
  paginationDetails: PaginationDetails;
  pageContent: PageContent<TItem>;
}
```

### Updating our store

With the updates made to the state, our store is no longer valid and we also need to propagate the generic type here

However, we don't want to use a concrete type yet or all our modifications would have been done for nothing

To address the fist compilation error, we will first need to also indicate our store that we will be using `TItem`:

```ts
@Injectable()
export abstract class PaginatedItemsComponentStore<TItem>
  extends ComponentStore<PaginatedItemsState<TItem>>
  implements OnStoreInit { /* ... */ }
```

After doing so, there is two small errors we need to address:

- In our `selectTodoItems` selector, `todoItems` does no longer exists since we have rename it to `items`. We can fix it by changing the property name:
  ```ts
  readonly selectItems = this.select(
    this.selectPageContent,
    ({ items }) => items
  );
  ```
- In our `updatePaginatedItems` updated, `PageContent` is not aware of the generic type and we need to specify it:
  ```ts
  private readonly updatePaginatedItems = this.updater(
    (state, pageContent: PageContent<TItem>) => ({ ...state, pageContent })
  );
  ```

However, we now face a bigger issue: in the `loadPage` effect, we are calling the `todoItemService` and this service is very specific to our `TodoItem`s

#### Delegate the fetching logic

From our `PaginatedItemsComponentStore`, there is no way for us to know in advance how a specific kind of `TItem` will be retrieved given an offset and the page size

However, a class that will know that is the implementing one

Fortunately, we are in an abstract class and we can let the child class define its own logic by adding an abstract method:

```ts
protected abstract getItems(paginationDetails: PaginationDetails): Observable<TItem[]>;
```

Using this method, we can now remove the instance of our service and replace its call by the abstract method:

```diff
-  private readonly _todoItemService = inject(TodoItemService);

  readonly loadPage = this.effect((trigger$: Observable<void>) => {
    return trigger$.pipe(
      withLatestFrom(this.selectPaginationDetails),
      switchMap(([, { offset, pageSize }]) =>
-       this._todoItemService.getTodoItems(offset, pageSize).pipe(
+       this.getItems({ offset, pageSize }).pipe(
          tapResponse(
-           (todoItems: TodoItem[]) => this.updatePaginatedItems({ todoItems }),
+           (items: TItem[]) => this.updatePaginatedItems({ items }),
            () => console.error("Something went wrong")
          )
        )
      )
    );
  });
```

#### Updating the `AppComponentStore`

We're  almost done! Now that our base store is generic, we need to specify in our `AppComponentStore` that `TItem` will be `TodoItem` for us


```ts
// 👇 Notice that we are now talking about `TodoItem`
const initialState: PaginatedItemsState<TodoItem> = {
  paginationDetails: {
    offset: 0,
    pageSize: 10,
  },
  pageContent: {
    items: [],
  },
};

@Injectable()
export class AppComponentStore
  // 👇 Same here
  extends PaginatedItemsComponentStore<TodoItem>
{
  readonly vm$ = this.select(
    // 👇 Don't forget that our selector has been renamed
    this.selectItems,
    (todoItems) => ({ todoItems }));
}
```

However, we now also need to implement that `getItems` method so that our parent component knows how to retrieve those `TodoItem`s

For that, we will need to reinject a `TodoItemService` instance and call it from there:

```ts
  private readonly _todoItemService = inject(TodoItemService);

  protected getItems({ offset, pageSize }: PaginationDetails): Observable<TodoItem[]> {
    return this._todoItemService.getTodoItems(offset, pageSize);
  }
```

Building our app again, everything should *still* be working as before but, this time, paginating a new type of entity won't need you to rewrite the whole component store again!

---

In this article we saw how to take advantage of generics to lift the common pagination logic to an abstract component that we can later extend

If you would like to go a bit further you can try to:

- Handle the loading and error logic
- Add extra selectors (first item of the page, etc.)
- Create a new `PostService` that is almost the same as the `TodoItemService` except that it is retrieving posts by calling `https://jsonplaceholder.typicode.com/posts`. You can then use this service to paginate `Post`s instead of `TodoItem`s by defining a new component store inheriting from `PaginatedItemsComponentStore<Post>`

> If you would like to check the resulting code, you can head on to the associated [GitHub repository](https://github.com/pBouillon/DEV.SimplePaginationWithNgRxComponentStores/tree/2.0.0)

---

I hope that you learnt something useful there and, as always, happy coding!

{% embed https://dev.to/pbouillon %}

---

Photo by <a href="https://unsplash.com/@roman34nov?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Roman Trifonov</a> on <a href="https://unsplash.com/photos/67rnodKzsRQ?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>