Angular Material UI Tutorial with NGRX

Spread the love

Angular Material UI Tutorial with NGRX

In this Angular Material UI Tutorial with NGRX, we will build an Angular 18 application using NGRX for state management and Angular Material 3 for the user interface. We will fetch data from a mock API using JSONPlaceholder and demonstrate how to implement actions, reducers, and effects for managing state in Angular. The tutorial will utilize standalone components, a feature introduced in Angular 18, making the process streamlined and modular.

By the end of this Angular Material UI Tutorial with NGRX, you will:

  1. Understand how to integrate NGRX into an Angular project.
  2. Learn how to use standalone components with Angular Material 3.
  3. Set up actions, reducers, and effects for NGRX.
  4. Fetch and display user data using NGRX and Angular Material components.

Prerequisites:

  • Basic knowledge of Angular and TypeScript.
  • Node.js and Angular CLI installed on your machine.

.

Angular material with ngrx tutorial for beginner

Table of Contents

  1. Project Setup with Standalone Components
  2. Installing Dependencies: NGRX and Angular Material
  3. Folder Structure for Standalone Angular 18
  4. Creating the User Model
  5. Setting Up NGRX (Actions, Reducers, and Effects)
  6. Creating the User Service
  7. Building the User Component with Angular Material 3
  8. Configuring the Main Application Component
  9. Running the Application
  10. Conclusion

1. Project Setup with Standalone Components

This section covers setting up a new Angular project using standalone components, which is a simpler and more modular approach introduced in Angular 18.

1.1 Install Angular CLI

If you haven’t installed Angular CLI globally yet, run the following command:

npm install -g @angular/cli

1.2 Create a New Angular Project with Standalone Components

We will create a new Angular 18 project with standalone components enabled:

ng new angular-ngrx-material-tutorial --standalone

This command will create a new Angular project using standalone components instead of the traditional NgModule system. Once the project is generated, navigate into the project folder:

cd angular-ngrx-material-tutorial

2. Installing Dependencies: NGRX and Angular Material

2.1 Install NGRX

NGRX helps manage complex state in Angular applications. Install the NGRX packages:

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

npm install @angular/platform-browser @angular/common @types/node --save

2.2 Install Angular Material 3

To style our application with Material Design, install Angular Material:

ng add @angular/material
ng add @angular/material

When prompted:

Choose a theme (e.g., Indigo/Pink).

Enable global typography and animations.

3. Folder Structure for Standalone Angular 18

Inside the src/app/ folder, create the following structure to keep the project organized:

angular material with ngrx tutorial

This structure will help organize our code as we develop the application.

4. Creating the User Model

The User model defines the structure of the user data. We will use this model across NGRX actions, reducers, and the component to display data.

In the models/ folder, create user.model.ts

export interface User {
  id: number;
  name: string;
  email: string;
}

This User interface includes:

id: A unique identifier for the user.

name: The user’s full name.

email: The user’s email address.

5. Setting Up NGRX (Actions, Reducers, and Effects)

5.1 Creating Actions

NGRX actions represent events that trigger state changes. In the store/ folder, create user.actions.ts

import { createAction, props } from '@ngrx/store';
import { User } from '../models/user.model';

export const loadUsers = createAction('[User List] Load Users');
export const loadUsersSuccess = createAction('[User List] Load Users Success', props<{ users: User[] }>());
export const loadUsersFailure = createAction('[User List] Load Users Failure', props<{ error: any }>());

These actions represent the different states of our user data loading process:

loadUsers: Initiates the user data loading.

loadUsersSuccess: Carries the loaded user data on success.

loadUsersFailure: Handles the error if loading fails.

5.2 Creating Reducers

Reducers handle the actual state change. In the store/ folder, create user.reducer.ts

// src/app/store/user.reducer.ts
import { createReducer, on } from '@ngrx/store';
import { loadUsers, loadUsersSuccess, loadUsersFailure } from './user.actions';
import { User } from '../models/user.model';

export interface UserState {
  users: User[]; // Array of users
  error: any;
}

const initialState: UserState = {
  users: [],      // Start with an empty array
  error: null
};

export const userReducer = createReducer(
  initialState,
  on(loadUsers, (state) => ({ ...state })), // No change to state on loadUsers
  on(loadUsersSuccess, (state, { users }) => ({ ...state, users })), // users should be an array here
  on(loadUsersFailure, (state, { error }) => ({ ...state, error }))
);

5.3 Creating Effects

NGRX effects handle side effects like making HTTP requests. In the store/ folder, create user.effects.ts

// src/app/store/user.effects.ts
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { inject } from '@angular/core';
import { UserService } from '../services/user.service';
import { loadUsers, loadUsersSuccess, loadUsersFailure } from './user.actions';
import { catchError, map, mergeMap } from 'rxjs/operators';
import { of } from 'rxjs';

@Injectable()
export class UserEffects {
  // Use `inject()` instead of constructor-based injection
  private actions$ = inject(Actions);
  private userService = inject(UserService);
  
  loadUsers$ = createEffect(() =>
    this.actions$.pipe(
      ofType(loadUsers),
      mergeMap(() =>
        this.userService.getUsers().pipe(
          map((users) => loadUsersSuccess({ users })), // Ensure `users` is an array here
          catchError((error) => of(loadUsersFailure({ error })))
        )
      )
    )
  );
}

This effect listens for the loadUsers action, fetches the user data via UserService, and dispatches either the loadUsersSuccess or loadUsersFailure action.

6. Creating the User Service

We will now create a service that fetches user data from a mock API. In the services/ folder, create user.service.ts:

// src/app/services/user.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { User } from '../models/user.model';

@Injectable({ providedIn: 'root' })
export class UserService {
  private apiUrl = 'https://jsonplaceholder.typicode.com/users';

  constructor(private http: HttpClient) {}

  getUsers(): Observable<User[]> {
    return this.http.get<User[]>(this.apiUrl); // Ensure this returns an array
  }
}

 

This service uses HttpClient to fetch the user data from JSONPlaceholder and returns the data as an observable.

7. Building the User Component with Angular Material 3

Now we will create the User List component using Angular’s standalone components and Angular Material for the UI.

In the components/ folder, create user-list.component.ts

// user-list.component.ts
import { Component, OnInit, inject } from '@angular/core';
import { Store } from '@ngrx/store';
import { UserState } from '../store/user.reducer';
import { loadUsers } from '../store/user.actions';
import { Observable } from 'rxjs';
import { User } from '../models/user.model';
import { MatTableDataSource } from '@angular/material/table'; // Import MatTableDataSource

// Import Angular Material Modules
import { MatTableModule } from '@angular/material/table';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-user-list',
  standalone: true,
  templateUrl: './user-list.component.html',
  styleUrls: ['./user-list.component.scss'],
  imports: [MatTableModule, MatButtonModule, MatCardModule, CommonModule], // Import necessary modules here
})
export class UserListComponent implements OnInit {
  private store = inject(Store<UserState>);
  dataLoaded = false;
  
  // Create a MatTableDataSource instance
  dataSource = new MatTableDataSource<User>();
  displayedColumns: string[] = ['id', 'name', 'email'];

  ngOnInit(): void {
    // Subscribe to users$ to update the dataSource
    this.store.select((state) => state.users.users).subscribe((users) => {
      if (users) {
        this.dataSource.data = users; // Set data for MatTableDataSource
      }
    });
  }

  loadUsers() {
    this.store.dispatch(loadUsers());
    this.dataLoaded = true;
  }
}
  • in the ngoninit method. The store.select method updates the Material Table data source with user data from the store whenever users state changes.
  • loadUsers(): Dispatches the loadUsers action when clicked, triggering the data loading process.

7.1 In the components/ folder, create user-list.component.html:

<mat-card>
    <!-- Site Header with centered Load Users button -->
    <header class="site-header">
      <span class="site-name">Programmer Blog</span>
      <button mat-raised-button color="primary" (click)="loadUsers()" [disabled]="dataLoaded" class="header-button">
        Load Users
      </button>
    </header>
  
    <!-- Display Angular Material Table once data is loaded -->
    <div class="table-container" *ngIf="dataLoaded">
      <table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
        <!-- ID Column -->
        <ng-container matColumnDef="id">
          <th mat-header-cell *matHeaderCellDef> ID </th>
          <td mat-cell *matCellDef="let user"> {{user.id}} </td>
        </ng-container>
  
        <!-- Name Column -->
        <ng-container matColumnDef="name">
          <th mat-header-cell *matHeaderCellDef> Name </th>
          <td mat-cell *matCellDef="let user"> {{user.name}} </td>
        </ng-container>
  
        <!-- Email Column -->
        <ng-container matColumnDef="email">
          <th mat-header-cell *matHeaderCellDef> Email </th>
          <td mat-cell *matCellDef="let user"> {{user.email}} </td>
        </ng-container>
  
        <!-- Header and Row Declarations -->
        <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
        <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
      </table>
    </div>
  
    <!-- Footer Section -->
    <footer class="site-footer">
      <span>© 2023 Programmer Blog</span>
    </footer>
  </mat-card>

7.2 In the components/ folder, create user-list.component.scss:

/* Style for the site header */
.site-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 16px;
    font-size: 18px;
    font-weight: bold;
    background-color: #f5f5f5;
    border-bottom: 1px solid #ddd;
    position: relative;
}

.site-name {
    font-size: 20px;
}

.header-button {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    background-color: #e0e0e0 !important;
    border-radius: 8px;
}

/* Style the table container to limit width and height */
.table-container {
    max-width: 80%;
    /* Limit the width of the table */
    margin: 20px auto;
    /* Center the table on the page with top and bottom margin */
    overflow: auto;
}

.mat-card {
    padding: 20px;
    margin: 20px auto;
    max-width: 90%;
}

table {
    width: 100%;
}

.mat-header-cell,
.mat-cell {
    padding: 8px 16px;
    /* Add padding to cells */
}

.mat-elevation-z8 {
    max-height: 500px;
    /* Limit table height */
    overflow-y: auto;
    /* Enable scrolling if content overflows */
}

/* Footer styling at the bottom of the card */
.site-footer {
    text-align: center;
    padding: 10px;
    font-size: 14px;
    color: #666;
    border-top: 1px solid #ddd;
    margin-top: 20px;
    /* Separate footer from table */
}

8. Configuring the Main Application Component

In Angular 18 standalone components, we don’t need a traditional NgModule. Instead, we configure the app using bootstrapApplication.

In src/main.ts, configure the app like this:

This file:

Bootstraps the AppComponent as the root component.

Registers the NGRX store and effects.

Includes HttpClient for making API requests.

8.1 Add code below to main.ts

import { bootstrapApplication } from '@angular/platform-browser';
import { provideHttpClient } from '@angular/common/http';
import { provideStore } from '@ngrx/store';
import { provideEffects } from '@ngrx/effects';
import { userReducer } from './app/store/user.reducer';
import { UserEffects } from './app/store/user.effects';
import { AppComponent } from './app/app.component';

bootstrapApplication(AppComponent, {
  providers: [
    provideHttpClient(),
    provideStore({ users: userReducer }),
    provideEffects([UserEffects]), // Ensure UserEffects is provided here
  ],
});

8.2 Add code below to app.component.ts

import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { UserListComponent } from './components/user-list.component';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet, UserListComponent],
  template: `<app-user-list></app-user-list>`,
  styleUrl: './app.component.scss'
})
export class AppComponent {
  title = 'angular-ngrx-material-tutorial';
}

9. Running the Application

Start the application using:

ng serve

Visit http://localhost:4200 in your browser. You should see the Load Users button. Once clicked, the user data will be fetched from the mock API and displayed in the list using Angular Material UI.

The initial screen for the project.

angular materiel ui with ngrx tutorial - initial load

When user clicks on the Load Users. the below screen displays the list of users.

angular material ui with ngrx tutorial - final

10. Conclusion

In this Angular Material UI Tutorial with NGRX, we created a full Angular 18 app using standalone components for simplicity and modularity. We learned how to set up NGRX for state management, handled actions, reducers, and effects, and fetched data from a mock API. Using Angular Material 3, we styled our UI and presented the user data in a clean, responsive interface.

You can download the source code of the tutorial from our GitHub repository.

angular materiel ui with ngrx tutorial - github reposirory

This Angular Material UI Tutorial with NGRX has shown how powerful and streamlined Angular 18’s new features are, making state management with NGRX easier to integrate into modern web apps.

Related Articles:

Previous Article