Angular Material Table with NgRx Library Learn it in 27 Steps.

Srikanth
13 min readFeb 11, 2023

--

Thanks for visiting my medium, this article gives you information about how to lazy load data in mat table and mat paginator and also implement the ngrx concept along with Angular material library.

Photo by Aleks Dorohovich on Unsplash

Step 0. Before starting the application, you need to know about Angular Material and NgRx. If you know about these, you can ignore this step.

Basically, Angular Material is used to develop the best user interfaces and it will give inbuilt components related to table, forms etc. Also, it will help developer to develop the user interfaces easily and customization of angular material is also easy. Most of the industries using the Angular material in their projects.

For more visit Angular Material UI component library

NgRx is the industrial approach to store the data and it is mostly used to store the data in the state in that way can be able to access it across the application due this we can develop the user interfaces and also pass the data across all the component hierarchy.

For more visit NgRx — @NGRX/STORE

I think you understood something, In the below example, I am going to implement Angular material and NgRx libraries both together to achieve lazy loading in the angular application.

Please find the below steps carefully and implement lazy loading using NgRx and Angular Material. Happy Coding !!!.

Step 1. Create an angular project with the below command.

ng new angular-material-table-with-ngrx-example

Step 2. After successful creation of the angular app, change the file directory to project-name. “cd angular-material-table-with-ngrx-example”.

Open the project in vs code using “code .” in terminal or open with vs code. Then run the project using “ng serve” in the terminal. Open project in chrome using localhost: 4200.

Step 3. Open the app component in vs code and remove the content which is created by angular CLI while creating the app.

For Adding angular material using the command

ng add @angular/material

Step 4. Select theme, am selecting Indigo/Pink, and click the below items as “yes.”

  • Set up global Angular Material typography styles? Am selecting y
  • Set up browser animations for Angular Material? (Y/n) Select ‘y’.

Step 5. Created Shared Module in the libs folder using “ng generate module shared”. And import, export material modules in “shared.module.ts”. And also add in the “app.module.ts”.

Step 6. Create the audits component in the “apps/components” folder. And add the audits component in router and path as audits.

Step 7. For adding “NgRx library” by using the command and install all libraries along with “@ngrx/Store” like @ngrx/effects @ngrx/store-devtools @ngrx/schematics, please find the command below.

Install all in once

npm install @ngrx/store @ngrx/effects @ngrx/store-devtools @ngrx/schematics --save

(OR)

Install one after another

npm install @ngrx/store
npm install @ngrx/effects
npm install @ngrx/store-devtools
npm install @ngrx/schematics

Step 8. Change default “angular cli” to “NgRx cli ” to install actions, reducers, and effects.

Don’t worry it won’t affect any angular-related commands.

ng config cli.defaultCollection @ngrx/schematics

Step 9. To generate store, state, and reducers setup please run the below command and give the root module to “app.module.ts”.

ng generate store AuditState --root --module app.module.ts

Step 10. After executing the above command reducers folder will be created and the “index.js” file will be there.

And Also, Inside the “app.module.ts” file imports array is updated with new lines of code.

The auto-generated “index.js” file will be like the below. In this “index.js” the state and reducer will be added by default after performing the above command.

Step 11. Please run the below commands to generate actions, effects, selectors, and reducers.

 ng generate action actions/audits
ng generate reducer reducers/audits
ng generate effect effects/audits
ng generate selector selectors/audits

Attached screenshot of the terminal window in which I ran the commands to generate action, effects, selectors, and reducers.

Step 12. So, the NgRx schematics are generated automatically with a few files contains with sample data, please find below the files.

  • Actions are in the sense calling the actions like run, catch, drop. For this project, we need audits data, so I created loadAudits to get the audits and loadAuditsSuccess for storing data, and loadUsersFailure action to store the error.
  • Effects are like interceptors/listeners whenever we are dispatching the action. At that time effect will be triggered and it will call the service, If the service gives a response, we will call the success action.

If it returns an error, we will call the failure action.

  • Reducers are mainly used for updating the store based on the actions.
  • Selectors are used to get the data from the updated state. the updated state will be available on the side of the reducer.

Step 13. After successfully generating all the files. Create Audit models for Audit state in app/store/models/ folder.

After completing this, please do “npm install” and after running the application by using “ng serve”.

If in case any error is found while running the application, please open the “package.json” file. Check the “NgRx versions” and “angular version” is the same or not. If it is not the same, please change the version like below. If no errors this process is not required.

After that check the app.module.ts file and change the effect statement forFeature to forRoot like below.

Step 14. Open app.module.ts and add below statement inside imports array to load redux dev tools in the application local environment to check the redux values.

If it is already available, please ignore this point.

!environment. production? StoreDevtoolsModule.instrument() : []

If you don’t know how to install chrome dev tools click on this link and click on add to chrome. If already did this one, please ignore it.

Open the project by using “localhost:4200” and inspecting it you can be able to see redux dev-tools with no state information.

Step 15. Open the “audits.action.ts” file and add the below actions. If the “ng generate action actions/users” command generated actions by default,

You can ignore this step. If you want to add new actions you can use them.

Step 16. Open the “index.ts” file from “app/store/reducers” and add the below changes.

  • First, import the Audit reducer in index.js by using the below statement.
import * as fromAuditReducer from './audits.reducer';
  • Add State of AuditReducer in state interface like below. In the same way, we can add multiple states as of now we are adding only one.
export interface State {
audits : fromAuditReducer.State;
}
  • Add reducer in ActionReducerMap, the same way we can add multiple reducers as of now we are adding only one.
export const reducers: ActionReducerMap<State> = {
audits : fromAuditReducer.reducer
};

Please find the overall file of index.ts for the test if you miss the code.

After successfully adding the above step, you can ably see the changes in the redux dev tools, for that, you need to run the application and go chrome dev tool so that you can be able to see the redux tab and you will see the empty state object.

Step 17. Open the “audits.reducers.ts ” file and add the below changes.

  • Import user’s actions by using the below statement in the side “audits.reducers.ts” file.
import * as AuditAuctions from "../actions/audits.actions";
  • Add data types in the side State interface like below.
export interface State{
loading: boolean;
errors: any;
audits: any[];
totalItems: number;
pageIndex: number;
pageSize: number;
}
  • Declare properties with default values inside the state object.
export const initialState: State = {
loading: false,
errors: null,
audits: [],
totalItems: 0,
pageIndex: 0,
pageSize: 10
};
  • Add actions inside the reducer function to update the state based on the action type.
export const reducer = createReducer(
initialState,
on(AuditAuctions.loadAudits, (state,{pageIndex,pageSize}) => ({
...state,
loading:false,
error:null,
pageIndex,
pageSize
})),
on(AuditAuctions.loadAuditsSuccess, (state, { data }) => ({
...state,
audits:data.audits,
loading: true,
totalItems:data.totalCount,
error: null
})),
on(AuditAuctions.loadAuditsFailure, (state,{error}) => ({...state,loading: false, error})),
);
  • After changing all these the overall file will look like the below.

Open the application and go to chrome dev tools click on the redux tab, you will see the values in the side audits object in redux dev tools.

Up to everything clear right, if anything did wrong, please check from first.

Step 18. Open the audits.selector.ts file and add the below lines of code to read data from the store.

  • First import AuditReducer by using the below statement.
import * as fromAuditReducer from '../reducers/audits.reducer';
  • To get the state object from audit reducer please add the below statement.
export const getAuditsState = createFeatureSelector<fromAuditReducer.State>('audits');
  • Add the below methods to get specific data from the store.
export const loading = createSelector(
getAuditsState,
(state: fromAuditReducer.State) => state.loading
);

export const getAudits = createSelector(
getAuditsState,
(state: fromAuditReducer.State) => state.audits
);

export const getTotalItems = createSelector(
getAuditsState,
(state: fromAuditReducer.State) => state.totalItems
);
  • The Overall file of audits.selectors.ts looks like the below please check if you miss anything or not.

Step 19. After all these do we need to add the changes in effects before that we need to create one service.

Create Audits Service under the “apis/” folder and add the below lines of code.

  • To generate a service file please use the below command and also add HttpClientModule inside “app. module.ts”.
ng generate service apis/audits.
  • Open the audits.service.ts file and add the below lines of code to call the service to get all audits.
private headers:HttpHeaders= new HttpHeaders({
'Content-Type':'application/json',
'Accept':"application/json",
'Access-Control-Allow-Methods':'GET,POST,PUT,DELETE',
'Authorization':''
});

constructor(private http:HttpClient) { }

public loadAudits(pageIndex,pageSize){
return this.http.get(`http://localhost:3000/public/audits/${pageIndex}/${pageSize}`,{headers :this.headers})
}

The overall file of audits.service.ts looks like the below, please check twice if you miss anything.

Step 20. Open the audits.effect.ts file and add the below lines of code.

  • Import Audits Service and add as a dependency in the constructor, also import Audit actions inside the effects.
import { Injectable } from '@angular/core';
import { Actions, createEffect } from '@ngrx/effects';
import { AuditsService } from '../apis/audits.service';

@Injectable()
export class AuditsEffects {

constructor(private actions$: Actions,private auditService: AuditsService) {}

}
  • Create load audits effect and add below lines of code inside that effect to call the audits service whenever the action is called and it will call effect, once the responce or error came from service it will call the other actions.
  loadAudits$ = createEffect(() => 
this.actions$.pipe(
ofType(AuditsActions.loadAudits),
map((action: any) => action),
mergeMap((action:any) => {
return this.auditService.loadAudits(action.pageIndex,action.pageSize).pipe(
map(data => AuditsActions.loadAuditsSuccess({ data })),
catchError(error => of(AuditsActions.loadAuditsFailure({ error })))
);
})
)
)
  • The Overall file of audits.effects.ts looks like the one below.

Step 21. Finally, we completed all the parts of code related to NgRx Store except dispatching the action and showing data in the UI.

Step 22. Open the “audits.component.ts” file, to load data and display data in the component. Please add the below lines of code.

  • Import store form store module and add it as a dependency in the constructor by using the below lines of code.
import { Component, OnInit } from '@angular/core';
import { Store } from '@ngrx/store';

@Component({
selector: 'app-audits',
templateUrl: './audits.component.html',
styleUrls: ['./audits.component.scss']
})
export class AuditsComponent implements OnInit {

constructor(private readonly store: Store) { }

ngOnInit(): void {
}

}
  • Import Audit actions by using the below statements.
import * as AuditsActions from "../../actions/audits.actions";
  • To call the action by using store dispatch method as below.
  public pageSize = 10;

constructor(private readonly store: Store) {
this.store.dispatch(AuditsActions.loadAudits({pageIndex: 0, pageSize: this.pageSize}));
}
  • After that import selectors by using the below statement and declare the audits$ variable to store data inside audits.component.ts.
import { Component, OnInit } from '@angular/core';
import { Store } from '@ngrx/store';
import * as AuditsActions from "../../actions/audits.actions";
import * as AuditsSelectors from "../../selectors/audits.selectors";


@Component({
selector: 'app-audits',
templateUrl: './audits.component.html',
styleUrls: ['./audits.component.scss']
})
export class AuditsComponent implements OnInit {

pageSize = 10;
audits$: any;
totalItems$: any;

constructor(private readonly store: Store) {
this.store.dispatch(AuditsActions.loadAudits({pageIndex: 0, pageSize: this.pageSize}));
}

ngOnInit(): void {
this.audits$ = this.store.select(AuditsSelectors.getAudits);
this.totalItems$ = this.store.select(AuditsSelectors.getTotalItems);
}

}

Once all the above points are completed, Open the application in the browser window and inspect the browser window click on the redux tab, You can able to see the data inside redux dev tools as below.

Step 23. Inside the audits.component.ts file create a variable for displaying columns.

public displayedColumns: any = ["uri",'email',"clientIp",'client_org',"serverIp",'server_org',"statusMessage","createdAt","updatedAt"]public totalCount=0;

Step 24. After that open audit.model.ts file and export the Audit Entry Interface.

Add the audit entry as an import statement inside audits.component.ts and give the audits$ variable type as AuditEntry[].

export interface AuditEntry{
clientIp: string;
clientIpDetails: IpDetails[]
createdAt: any;
email: string;
hostname: string;
serverIp: string;
serverIpDetails: IpDetails[];
statusCode: string | number;
statusMessage: any;
updatedAt: any;
uri: any;
_id: string | number;
}

export interface IpDetails{
city: string;
country: string;
ip: string;
loc: any;
org: string;
postal: any;
readme: any;
region: any;
timezone: any;
_id: string | number
}
  • Import AuditEntry entry inside audits.component.ts
import { AuditEntry } from 'src/app/models/audit.model';

Step 25. Oops!!!, we have not added HTML content. Add HTML content for displaying the table as below.

  • set displayedColumns [] to two items e.g. [createdAt, updatedAt]
<div class="row mt-2" *ngIf="audits$ | async as audits">
<table class="table" mat-table [dataSource]="audits" style="width: 100%;" border="0">
<ng-container matColumnDef="createdAt">
<th mat-header-cell *matHeaderCellDef> CREATED AT </th>
<td mat-cell *matCellDef="let element"> {{element.createdAt}} </td>
</ng-container>

<ng-container matColumnDef="updatedAt">
<th mat-header-cell *matHeaderCellDef> UPDATED AT </th>
<td mat-cell *matCellDef="let element"> {{element.updatedAt}} </td>
</ng-container>

<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</div>
  • After that add all the columns to displayedColumns[] and add HTML related to columns inside the table. Test the UI in the browser for changes.
<div class="container">
<div class="row" *ngIf="audits$ | async as audits">
<table class="table" mat-table [dataSource]="audits" style="width: 100%;" border="0">
<ng-container matColumnDef="createdAt">
<th mat-header-cell *matHeaderCellDef> CREATED AT </th>
<td mat-cell *matCellDef="let element"> {{element.createdAt}} </td>
</ng-container>

<ng-container matColumnDef="updatedAt">
<th mat-header-cell *matHeaderCellDef> UPDATED AT </th>
<td mat-cell *matCellDef="let element"> {{element.updatedAt}} </td>
</ng-container>

<ng-container matColumnDef="email">
<th mat-header-cell *matHeaderCellDef> EMAIL </th>
<td mat-cell *matCellDef="let element"> {{element.email ? element.email : 'Email Not Entered'}} </td>
</ng-container>

<ng-container matColumnDef="serverIp">
<th mat-header-cell *matHeaderCellDef> SERVER IP </th>
<td mat-cell *matCellDef="let element"> {{element.serverIp}} </td>
</ng-container>

<ng-container matColumnDef="clientIp">
<th mat-header-cell *matHeaderCellDef> CLIENT IP </th>
<td mat-cell *matCellDef="let element"> {{element.clientIp}} </td>
</ng-container>

<ng-container matColumnDef="statusCode">
<th mat-header-cell *matHeaderCellDef> STATUS CODE </th>
<td mat-cell *matCellDef="let element"> {{element.statusCode}} </td>
</ng-container>

<ng-container matColumnDef="statusMessage">
<th mat-header-cell *matHeaderCellDef> STATUS MESSAGE </th>
<td mat-cell *matCellDef="let element">
<span *ngIf="element.statusMessage === 'OK'" style="font-weight: bolder;color:green;">LOGIN SUCCESS</span>
<span *ngIf="element.statusMessage !== 'OK'" style="font-weight: bolder;color:red;">LOGIN FAIL</span>
</td>
</ng-container>

<ng-container matColumnDef="uri">
<th mat-header-cell *matHeaderCellDef> URI </th>
<td mat-cell *matCellDef="let element"> {{element.uri}} </td>
</ng-container>

<ng-container *ngFor="let field of ['clientIpDetails','serverIpDetails']">
<ng-container matColumnDef="{{field.includes('client')? 'client_org' : 'server_org'}}">
<th mat-header-cell *matHeaderCellDef> {{field.includes('client')? 'CLIENT ' : 'SERVER '}}ORG </th>
<td mat-cell *matCellDef="let element"> {{element[field][0].org}} </td>
</ng-container>
</ng-container>

<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</div>
</div>

Step 26. After that Add a paginator tag below the table to display page options for lazy loading. And create pageNavigate(page:PageEvent) method inside audits.component.ts add call the loadAudits store action to load audits.

<mat-paginator [length]="totalItems$ | async"
[pageSize]="pageSize"
[pageSizeOptions]="[5, 10, 25, 100]"
(page)="pageNavigate($event)"
aria-label="Select page">
</mat-paginator>
public pageNavigate(event:PageEvent){
this.store.dispatch(AuditsActions.loadAudits({pageIndex: event.pageIndex, pageSize: event.pageSize}));
}

The overall file of audits.component.html and audits.component.ts will be mentioned below please check.

audit.component.html
audits.component.ts

Step 27. Hey !!! I saw the data in the browser chrome dev tools and UI, While loading the page the audits API is calling and storing the data inside the store. Inside the Audits component,when you click on paginator the load audits action will be called and parallelly the audits api also called the selector will take the updated data of audits from the store. This way we can load the data lazily while call the paginator using NgRx. Please check the below output.

Output:

output
Source:

Frontend: https://github.com/mryenagandula/angular-material-table-with-ngrx-example

Backend: https://github.com/mryenagandula/Letstalk-Backend

Live Backend URI:
https://letstalk-be.herokuapp.com/

Thanks for reading my article, please share your feedback, claps, and comments. In that way, it will have helped me to improve my articles in the future. Please share my story with your near and dear, Also follow and subscribe to the medium.

--

--

Srikanth

Senior Software Engineer | Medium Articles | Storyteller | Quick Learner | React JS | Angular | Java | Learn Something new from my stories