Angular - NGRX-Data - AG Grid - Pt. 1
Tags: angular, ngrx, aggrid, javascript
Published: 2019-12-24 20:45:07

---
title: Angular - NGRX-Data - AG Grid - Pt. 1
published: true
description: Create a Dashboard application quickly with Angular, NGRX and AG Grid.
tags: angular, ngrx, ag-grid, javascript
---

## Introduction

I've been building a Dashboard-style monitoring application for a client for the last few weeks. The application requires CRUD functionality across a series of resources. Although there is a lot of shared functionality across these different resources, each one has a set of business rules when it comes to creating, updating and destroying these resources. When starting out, I had to select a few libraries that would help me avoid having to write all of the common CRUD functionality while allowing me to easily insert the business logic at any point.

After some research, I settled on NGRX-Data for state management and AG Grid for the resource views. You might've heard some criticism around NGRX on how much boilerplate it requires but I want to clarify that NGRX Data is an additional layer of abstraction on top of the basic NGRX Store library which helps the developer avoid the common boilerplate code. In fact, I found myself writting very little code beyond configuration to get the majority of the necessary functionality going.

As for the UI, I chose AG Grid since it comes with tons of functionality out of the box and is very easy to extend. It comes with sensible defaults while also offering tons of extension points. I have yet to find any significant limitation on this library and I definetly recommend it's use for an application requiring anything beyond a trivial data table.

Finally, we'll be leveraging the Angular 2+ web application framework and the RxJs library. Be sure to understand both of these tools to follow along although this post will be more focused on NGRX Data and AG Grid.

## Demo Data

I'll be using data from [JSON Placeholder](https://jsonplaceholder.typicode.com/) which is a free-to-use, mock API. It doesn't belong to me so much gratitude to [Typicode](https://github.com/typicode) for making this awesome tool available.

## Installation

### Creating an Angular project

Let's get our application setup. First, start a new Angular 2+ project. If you don't already have the `@angular/cli` installed, run the following:

```sh
npm i -g @angular/cli
```

Be sure to include routing and SCSS in the Angular application prompts.

```sh
ng new ngrx-data-ag-grid-demo
cd ngrx-data-ag-grid-demo
```

### Install AG Grid:

```sh
npm install --save ag-grid-community ag-grid-angular
```

We need to add some styles for AG Grid to our `styles.scss` file.

```scss
@import "~ag-grid-community/dist/styles/ag-grid.css";
@import "~ag-grid-community/dist/styles/ag-theme-balham.css";
```

### Install NGRX Data

```sh
npm i --save @ngrx/data @ngrx/store @ngrx/entity @ngrx/effects
```

NGRX Data still requires NGRX Store, Effects and Entities. It does however add a lot of the functionality for CRUD actions freeing up developers to focus on the business domain. Create an `app-store.module.ts` file and add the following:

```typescript
import { NgModule } from "@angular/core";
import { CommonModule } from "@angular/common";
import { StoreModule } from "@ngrx/store";
import { EffectsModule } from "@ngrx/effects";
import { EntityDataModule, DefaultDataServiceConfig } from "@ngrx/data";

import { PostCollectionService } from "./posts/post-collection.service";

import * as fromPosts from "./posts";

const NGRX_STORE_CONFIGURATION = {};

const REGISTERED_EFFECTS = [];

const ENTITY_METADATA = {};

const ENTITY_PLURAL_NAMES = {};

const NGRX_DATA_SERVICE_CONFIGURATION = {};

@NgModule({
  imports: [
    CommonModule,
    StoreModule.forRoot(NGRX_STORE_CONFIGURATION),
    EffectsModule.forRoot(REGISTERED_EFFECTS),
    EntityDataModule.forRoot({
      entityMetadata: ENTITY_METADATA,
      pluralNames: ENTITY_PLURAL_NAMES
    })
  ],
  providers: [
    {
      provide: DefaultDataServiceConfig,
      useValue: NGRX_DATA_SERVICE_CONFIGURATION
    },
    PostCollectionService
  ]
})
export class AppStoreModule {}
```

### Configuring the API endpoint

Configure the API address by providing a `DefaultDataServiceConfig` object. Add the following to `app-store.module.ts`:

```typescript
...
const NGRX_DATA_SERVICE_CONFIGURATION = {
  root: "https://jsonplaceholder.typicode.com/"
};
...
```

### Add the Store to the App

Import the `AppStoreModule` within the `app.module.ts`:

```typescript
import { BrowserModule } from "@angular/platform-browser";
import { NgModule } from "@angular/core";

import { AppRoutingModule } from "./app-routing.module";
import { AppStoreModule } from "./app-store.module";
import { AppComponent } from "./app.component";

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, AppStoreModule, AppRoutingModule],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {}
```

## Configuring the first Entity Collection

NGRX Data is focused on entities which are just collections of JS objects. It'll handle the synchronization of a local cache and a remote endpoint by default with pessimistic strategies. It can however be configured to use optimistic strategies, multiple endpoints, etc. All defaults can be overriden.

### Define the Entity state and configuration

The first entity will be the `Post` entity. Start by creating a `posts` directory and a `state.ts` file and an `index.ts` file. Add the following to `state.ts`:

```typescript
export const entityCollectionName = "Post";

export const pluralizedEntityName = "posts";

export const entityCollectionEndpoint = pluralizedEntityName;

export interface Post {
  id: number;
  userId: number;
  title: string;
  body: string;
}
```

### Export the Entity state and configuration

And, `index.ts`:

```typescript
export * from "./state";
```

### Configure the Entity in the Store

The `app-store.module.ts` needs to be updated with the `Post` entity collection configuration:

```typescript
...
import * as fromPosts from './posts';
...
const ENTITY_METADATA = {
  [fromPosts.entityCollectionName]: {}
};
...
const ENTITY_PLURAL_NAMES = {
  [fromPosts.entityCollectionName]: fromPosts.pluralizedEntityName
};
...
```

NGRX Data has a default pluralization feature based on the collection name but we found it to be very unreliable. We decided to always provide the pluralNames from configuration instead. This also makes mapping application routes to API calls more reliable.

### Creating the Entity Collection Service

NGRX Data provides the `EntityCollectionServiceBase` class which provides the high-level implementation for the observable state and actions of the Entity store. Each Entity will have a dedicated service that extends this class.

Create a file within the `posts` directory named `post-collection.service.ts` and add the following:

```typescript
import { Injectable } from "@angular/core";
import { EntityCollectionServiceBase } from "@ngrx/data";
import { EntityCollectionServiceElementsFactory } from "@ngrx/data";

import * as fromPosts from "./";

@Injectable()
export class PostCollectionService extends EntityCollectionServiceBase<
  fromPosts.Post
> {
  constructor(
    readonly elementsFactory: EntityCollectionServiceElementsFactory
  ) {
    super(fromPosts.entityCollectionName, elementsFactory);
  }
}
```

### Display the data with AG Grid

Create a directory within the `posts` directory named `posts-list` and add a `posts-list.component.ts` file. Add the following:

```typescript
import { Component } from "@angular/core";
import { concat } from "rxjs";
import { startWith } from "rxjs/operators";
import { FirstDataRenderedEvent } from "ag-grid-community";

import { PostCollectionService } from "../post-collection.service";

@Component({
  selector: "app-posts-list",
  template: `
    <h1>Posts</h1>
    <hr />
    <ag-grid-angular
      class="ag-theme-balham grid"
      [columnDefs]="columns"
      [rowData]="rows$ | async"
      [pagination]="true"
      [paginationAutoPageSize]="true"
      (firstDataRendered)="onFirstDataRendered($event)"
    ></ag-grid-angular>
  `,
  styles: [
    `
      :host {
        display: flex;
        flex-direction: column;
        justify-content: center;
        padding-left: 5vw;
      }

      .grid {
        height: 80vh;
        width: 90vw;
      }
    `
  ]
})
export class PostListComponent {
  private columnDefaults = {
    resizable: true,
    sortable: true,
    filter: true
  };

  readonly columns = [
    {
      ...this.columnDefaults,
      headerName: "ID",
      field: "id",
      resizable: false
    },
    {
      ...this.columnDefaults,
      headerName: "Title",
      field: "title"
    },
    {
      ...this.columnDefaults,
      headerName: "Body",
      field: "body"
    }
  ];

  readonly rows$ = concat(
    this.postCollectionService.getAll(),
    this.postCollectionService.entities$
  ).pipe(startWith(null));

  constructor(private postCollectionService: PostCollectionService) {}

  onFirstDataRendered({ columnApi }: FirstDataRenderedEvent): void {
    columnApi.autoSizeAllColumns();
  }
}
```

## Setup lazy loading for Feature modules

This is a great opportunity to setup lazy-loading of each feature module. We'll load the proper presentation components based on the current route.

First, create a `posts-routing.module.ts` and add the following:

```typescript
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { PostListComponent } from "./posts-list/posts-list.component";

const routes: Routes = [
  {
    path: "",
    component: PostListComponent
  }
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class PostsRoutingModule {}
```

Second, create a `posts.module.ts` and add the following:

```typescript
import { NgModule } from "@angular/core";
import { CommonModule } from "@angular/common";
import { AgGridModule } from "ag-grid-angular";

import { PostsRoutingModule } from "./posts-routing.module";
import { PostListComponent } from "./posts-list/posts-list.component";

const AG_GRID_CUSTOM_COMPONENTS = [];

@NgModule({
  imports: [
    CommonModule,
    AgGridModule.withComponents(AG_GRID_CUSTOM_COMPONENTS),
    PostsRoutingModule
  ],
  declarations: [PostListComponent]
})
export class PostsModule {}
```

Next, add the router outlet to the `app.component.html` file:

```html
<router-outlet></router-outlet>
```

Finally, add the first route to the `app-routing.module.ts`:

```typescript
...
import * as fromPosts from './posts';
...
const routes: Routes = [
  {
    path: fromPosts.entityCollectionEndpoint,
    loadChildren: () => import("./posts/posts.module").then(m => m.PostsModule)
  }
];
...
```

We should now be able to navigate in our browser to `http://localhost:4200/posts` and see a grid populated with data from JSONPlaceholder. Not bad for how little code we've had to write.

## Conclusion

For Part 2, we'll be adding the `User` entity and interpolating the author's name into each of the `Post` entries in the AG Grid.