Angular Material 16 MEAN Stack SPA CRUD Tutorial

Last Updated on by in Angular

In this tutorial, we will find out the best way to build an Angular MEAN Stack single-page CRUD web application from scratch. We will use the Angular Material UI package to design the UI; On the other hand, to build the backend, we will use MongoDB, Express js, and Node js.In this MEAN stack tutorial, you’ll learn to develop and set up a MEAN stack project from scratch. I’ll be creating back-end and front-end for a real-world CRUD web application from scratch.

For the demo purpose, I’ll create a students record management CRUD (create, read, update & delete) web application. In this CRUD app user will be able to perform the following tasks:

  • Add student ID
  • Add student name
  • Add student email
  • Add section Angular Material dropdown
  • Add multiple subjects using Angular material input chips
  • Add student’s gender using Angular material radio buttons
  • Add student date of birth using Angular material datepicker

Following topics will be covered in this tutorial:

Angular Project Setup

  • Setting up Node js
  • Setting up Angular CLI
  • Installing & setting up Angular project
  • Creating routes to navigate between components
  • Creating Angular service to manage CRUD operations
  • Consuming RESTful APIs using Angular Service

Angular Material UI Library

  • Setting up an Angular material ui library in a real-world Angular application.
  • Creating web application’s front-end using Angular material ui components like :- Angular material default theme, icons, buttons, navbar, date-picker, form, data tables and chip inputs.

MEAN Stack Back-end Setup

  • Set up MongoDB in Angular MEAN stack app.
  • Setting up Express js server with Node js.
  • Creating RESTful APIs with Node js and Express js.

Workflow of MEAN Stack Angular Material Tutorial

I’ll create application’s frontend using Angular material 11 UI components and backend with Node js, Express js and MongoDb. To make it developer friendly I’ll create a separate project for frontend and backend.

I will be building RESTful API using MEAN stack backend and will use those APIs with Angular service to consume the data.

Following technologies, will be used throughout the tutorial.

  • NPM v6.4.1
  • Node v10.15.3
  • RxJS V6.5.2
  • Angular v8.0.0
  • AngularCLI v8.0.0
  • MongoDB 4.0.6
  • MongoDB shell v4.0.6

Installing Node JS and Angular CLI

Firstly, you need to have Node.js and Angular CLI installed in your system to work with Angular Mean stack project. To install Node.js in your system, follow this tutorial How To Install Node JS on Your System?

Node.js will help us to install the required dependencies for this Mean stack project.

In the next step, we’ll be installing Angular CLI with the help of NPM. Now with the help of Angular CLI, we’ll install the new Mean stack project.

npm install @angular/cli -g

We’ve successfully installed Node.js and Angular CLI by now. Now we can use the ng command to generate new Angular project, components, services, routing or many more features of Angular.

Angular Project setup

We are going to build a MEAN stack web app using Angular. In our MEAN stack web app, we’ll use the Angular framework to create the frontend of the app. Run the below command to generate a new angular project.

ng new angular-material-mean-stack

Answer some Angular CLI questions:

# ? Would you like to add Angular routing? = Yes

# ? Which stylesheet format would you like to use? = SCSS

Head over to the newly created project folder.

cd angular-material-mean-stack

In order to remove strict type warnings or errors make sure to set “strict”: false and "strictTemplates": false under compilerOptions and angularCompilerOptions properties in tsconfig.json file.

In next step we’ll create three new components to manage Mean stack Angular CRUD app. Use Angular CLI to generate Angular components:

ng g component components/add-student --module app
ng g component components/edit-student --module app
ng g component components/students-list --module app

We are using --module app parameter because we have 2 module files in the app folder. Now with the –module app parameter We are telling Angular CLI that app.module.ts is our main app module file.

No Property Access From Index Signature

To resolve error:

Property ‘xxxName’ comes from an index signature, so it must be accessed with [‘xxxName’]

This setting makes sure profound consistency between accessing a field via the “dot” (obj.key) syntax, and “indexed” (obj["key"]) and the way which the property is declared in the type.

Without this flag, TypeScript will allow you to use the dot syntax to access fields which are not defined:

Make sure to set noPropertyAccessFromIndexSignature property to false under compilerOptions in tsconfig.json file:

"compilerOptions": {
 ...
 ...
   "strict": false,
   "noPropertyAccessFromIndexSignature": false,
   "strictTemplates": false
 ...
 ...
}

Setting up Routes to navigate between components.

In this part of the tutorial we’ll create routes in our Mean stack Angular CRUD app. Routes allow us to navigate between components in Angular app, update code in app-routing.module.ts file.

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { AddStudentComponent } from './components/add-student/add-student.component';
import { EditStudentComponent } from './components/edit-student/edit-student.component';
import { StudentsListComponent } from './components/students-list/students-list.component';

const routes: Routes = [
  { path: '', pathMatch: 'full', redirectTo: 'add-student' },
  { path: 'add-student', component: AddStudentComponent },
  { path: 'edit-student/:id', component: EditStudentComponent },
  { path: 'students-list', component: StudentsListComponent }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})

export class AppRoutingModule { }

Setting up Angular Material UI Library in Angular project

We’ll be using Angular Material UI library to build students record management system. I will help you to create a beautiful responsive layout with Angular material ui components. We’ll create Mean stack CRUD app with following Angular material UI components:

  • Angular material default theme
  • Angular material date-picker
  • Angular material icons
  • Angular material buttons
  • Angular material navbar
  • Angular material form
  • Angular material data tables
  • Angular material chip inputs

Run the following command to setup Angular material.

ng add @angular/material

Choose the Angular material theme as per your choice:

? Choose a prebuilt theme name, or "custom" for a custom theme: Indigo/Pink

❯ Indigo/Pink        [ Preview: https://material.angular.io?theme=indigo-pink ] 
  Deep Purple/Amber  [ Preview: https://material.angular.io?theme=deeppurple-amber ] 
  Pink/Blue Grey     [ Preview: https://material.angular.io?theme=pink-bluegrey ] 
  Purple/Green       [ Preview: https://material.angular.io?theme=purple-green ]

We’ve installed Angular material UI library in Mean stack project. Now we’ll create a separate material.module.ts file.

In this file we’ll import the various Angular material service so that we can use it and manage centrally in our Angular CRUD web app.

In next step we’ll create a custom Angular material module, Create src > app > material.module.ts file and import the following Angular material UI components in this file like given below.

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

import { MatBadgeModule } from '@angular/material/badge';
import { MatButtonModule } from '@angular/material/button';
import { MatChipsModule } from '@angular/material/chips';
import { MatNativeDateModule } from '@angular/material/core';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatGridListModule } from '@angular/material/grid-list';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatListModule } from '@angular/material/list';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatRadioModule } from '@angular/material/radio';
import { MatSelectModule } from '@angular/material/select';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatTableModule } from '@angular/material/table';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatTooltipModule } from '@angular/material/tooltip';

@NgModule({
  imports: [
    CommonModule,
    MatButtonModule,
    MatToolbarModule,
    MatIconModule,
    MatSidenavModule,
    MatBadgeModule,
    MatListModule,
    MatGridListModule,
    MatInputModule,
    MatFormFieldModule,
    MatSelectModule,
    MatRadioModule,
    MatDatepickerModule,
    MatNativeDateModule,
    MatChipsModule,
    MatTooltipModule,
    MatTableModule,
    MatPaginatorModule,
  ],
  exports: [
    MatButtonModule,
    MatToolbarModule,
    MatIconModule,
    MatSidenavModule,
    MatBadgeModule,
    MatListModule,
    MatGridListModule,
    MatInputModule,
    MatFormFieldModule,
    MatSelectModule,
    MatRadioModule,
    MatDatepickerModule,
    MatChipsModule,
    MatTooltipModule,
    MatTableModule,
    MatPaginatorModule,
  ],
  providers: [MatDatepickerModule],
})

export class AngularMaterialModule {}

Go to app.module.ts file and import the AngularMaterialModule.

import { BrowserModule } from '@angular/platform-browser';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { AddStudentComponent } from './components/add-student/add-student.component';
import { EditStudentComponent } from './components/edit-student/edit-student.component';
import { StudentsListComponent } from './components/students-list/students-list.component';

import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { AngularMaterialModule } from './material.module';
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { ApiService } from './shared/api.service';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';

@NgModule({
  declarations: [
    AppComponent,
    AddStudentComponent,
    EditStudentComponent,
    StudentsListComponent,
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    BrowserAnimationsModule,
    AngularMaterialModule,
    HttpClientModule,
    FormsModule,
    ReactiveFormsModule,
  ],
  providers: [ApiService],
  bootstrap: [AppComponent],
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
})

export class AppModule {}

We have already imported couple of services and modules, we will need them later. Don’t worry, if any error occurs follow the rest of the guide.

Setup a basic layout with Angular Material

Go to app.component.html file and include the following code.

<!-- Toolbar -->
<mat-toolbar color="primary" class="header">
  <div>Student Records</div>
  <span class="nav-tool-items">
    <mat-icon (click)="sidenav.toggle()" class="hamburger">menu</mat-icon>
  </span>
</mat-toolbar>

<mat-sidenav-container>
  <!-- Sidenav -->
  <mat-sidenav #sidenav [mode]="isBiggerScreen() ? 'over' : 'side'" [(opened)]="opened" [fixedInViewport]="true"
    [fixedTopGap]>
    <mat-nav-list>
      <a mat-list-item routerLinkActive="active" routerLink="/add-student">
        <mat-icon>add</mat-icon> Add Student
      </a>
      <a mat-list-item routerLinkActive="active" routerLink="/students-list">
        <mat-icon>format_list_bulleted</mat-icon> View Students
      </a>
    </mat-nav-list>
  </mat-sidenav>

  <!-- Main content -->
  <mat-sidenav-content>
    <router-outlet></router-outlet>
  </mat-sidenav-content>
</mat-sidenav-container>

Add the following code in app.component.ts file.

import { Component, ViewChild, HostListener, OnInit } from '@angular/core';
import { MatSidenav } from '@angular/material/sidenav';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
})
export class AppComponent {
  opened = true;
  @ViewChild('sidenav', { static: true }) sidenav: MatSidenav;

  ngOnInit() {
    if (window.innerWidth < 768) {
      this.sidenav.fixedTopGap = 55;
      this.opened = false;
    } else {
      this.sidenav.fixedTopGap = 55;
      this.opened = true;
    }
  }

  @HostListener('window:resize', ['$event'])
  onResize(event) {
    if (event.target.innerWidth < 768) {
      this.sidenav.fixedTopGap = 55;
      this.opened = false;
    } else {
      this.sidenav.fixedTopGap = 55;
      this.opened = true;
    }
  }

  isBiggerScreen() {
    const width =
      window.innerWidth ||
      document.documentElement.clientWidth ||
      document.body.clientWidth;
    if (width < 768) {
      return true;
    } else {
      return false;
    }
  }
}

To set up the style add the following code in styles.scss file.

html,body{height:100%;}
body{margin:0;font-family:'Roboto', sans-serif;}
.header{justify-content:space-between;}
.user-profile{margin-left:15px;}
.mat-sidenav-container{height:100%;display:flex;flex:1 1 auto;}
.mat-nav-list .mat-list-item{font-size:15px;}
.nav-tool-items{display:inline-block;margin-right:13px;}
.user-profile{margin-left:15px;cursor:pointer;}
.hamburger{visibility:hidden !important;}
.mat-sidenav,.mat-sidenav-content{padding:15px;}
.mat-list-item.active{background:rgba(0, 0, 0, .04);}
.mat-sidenav-content{padding:25px 40px 0;}
.mat-sidenav{background-color:#F2F2F2;width:250px;}
.header{position:sticky;position:-webkit-sticky;top:0;z-index:1000;}
mat-sidenav mat-icon{margin-right:12px;}
.hamburger{margin-top:5px;cursor:pointer;}
.mat-radio-button,.mat-radio-group{margin-right:25px;}
.controlers-wrapper>*{width:100%;padding:0;}
.misc-bottom-padding{margin:8px 0 10px;}
.misc-bottom-padding mat-label{margin-right:15px;}
mat-radio-group mat-radio-button{margin-left:5px;}
.button-wrapper button{margin-right:5px;}
table.mat-table,table{width:100%;}
.inner-wrapper{padding:15px 0 130px;width:100%;}
.inner-wrapper mat-card{display:inline-block;margin:0 6% 0 0;vertical-align:top;width:44%;}
.full-wrapper{width:100%;}
.multiple-items{position:relative;}
.multiple-items .tooltip-info{right:0;top:7px;cursor:pointer;color:#a1a7c7;position:absolute;font-size:20px;}
body .push-right{margin-right:10px;}
.no-data{text-align:center;padding-top:30px;color:#6c75a9;}
.button-wrapper{margin:20px 0 0 0;}
@media (max-width:1024px){.inner-wrapper mat-card{width:100%;}
 .mat-sidenav-content{padding:20px 20px 0;}
 .misc-bottom-padding mat-label{display:block;padding-bottom:10px;}
 .mat-sidenav{width:230px;}
 .mat-nav-list .mat-list-item{font-size:14px;}
}
@media (max-width:767px){.nav-tool-items{margin-right:0;}
 .hamburger{visibility:visible !important;}
}

Your basic layout is ready ready with Angular material library, in next step we’ll set up backend using node js, express js and mongoDB.

Build Mean Stack Backend with MongoDB, Node JS and Express JS

In this part of the tutorial, we are going to build a robust Mean stack backend using mongoDB, node js, and express js.

Following topics will be covered in this part of the tutorial:

  • Create a separate project for Mean stack backend.
  • Install required dependencies using NPM: body-parser, cors, express js, mongoose, and nodemon.
  • Set up MongoDB Database connection in Mean stack app to access MongoDB database using MongoDB Shell.
  • Define a data model with mongoose JS in Mean stack project.
  • Create RESTful APIs with Express js Routes in Mean Stack Project.
  • Configure Angular Mean Stack backend

Create a separate project for Mean stack backend.

In order to set up a separate Mean stack backend create a folder by the name of backend in the Angular’s root directory.

mkdir backend && cd backend

You’ve created the backend folder and entered into the project.

Next thing is to create a separate package.json for your Mean stack backend.

npm init -y

Install required dependencies using NPM: body-parser, cors, express js, mongoose, and nodemon.

After that install the required dependencies for your Mean stack app.

npm install --save express mongoose cors body-parser

Then install nodemon package it will save us from restarting the server every-time we make the changes in our backend code.

npm install nodemon --save-dev

Define Student data model with mongoose JS in Mean stack app.

We’ll create a model folder, inside the model folder we’ll create a Student Schema for students collection in MongoDB. Paste the below code in the model > Student.js file.

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

// Define collection and schema
let Student = new Schema({
  student_name: {
    type: String
  },
  student_email: {
    type: String
  },
  section: {
    type: String
  },
  subjects: {
    type: Array
  },
  gender: {
    type: String
  },
  dob: {
    type: Date
  }
}, {
  collection: 'students'
})

module.exports = mongoose.model('Student', Student)

Create RESTful APIs with Express js Routes in Mean Stack Project.

In this Angular Mean stack tutorial we are going to create RESTful APIs using Express js and Node js.

I will create a routes folder inside the backend folder and create a student.routes.js file.

Enter the below command to create the routes/ folder and student.routes.js file.

mkdir routes && cd routes && touch student.route.js

We’ve created RESTful APIs using Express js and Student Model, now Go to student.route.js file and add the following code.

const express = require("express");
const app = express();
const studentRoute = express.Router();
const cors = require("cors");

// Student model
let Student = require("../model/Student");

// CORS OPTIONS
var whitelist = ["http://localhost:4200", "http://localhost:8000"];
var corsOptionsDelegate = function (req, callback) {
  var corsOptions;
  if (whitelist.indexOf(req.header("Origin")) !== -1) {
    corsOptions = {
      origin: "*",
      methods: "GET,HEAD,PUT,PATCH,POST,DELETE",
    };
  } else {
    corsOptions = { origin: false }; // disable CORS for this request
  }
  callback(null, corsOptions);
};

// Add Student
studentRoute.route("/add-student").post(async (req, res, next) => {
  await Student.create(req.body)
    .then((result) => {
      res.json({
        data: result,
        message: "Data successfully added!",
        status: 200,
      });
    })
    .catch((err) => {
      return next(err);
    });
});

// Get all student
studentRoute
  .route("/", cors(corsOptionsDelegate))
  .get(async (req, res, next) => {
    await Student.find()
      .then((result) => {
        res.writeHead(201, { "Content-Type": "application/json" });
        res.end(JSON.stringify(result));
      })
      .catch((err) => {
        return next(err);
      });
  });

// Get single student
studentRoute.route("/read-student/:id").get(async (req, res, next) => {
  await Student.findById(req.params.id, req.body)
    .then((result) => {
      res.json({
        data: result,
        message: "Data successfully retrieved.",
        status: 200,
      });
    })
    .catch((err) => {
      return next(err);
    });
});

// Update student
studentRoute.route("/update-student/:id").put(async (req, res, next) => {
  await Student.findByIdAndUpdate(req.params.id, {
    $set: req.body,
  })
    .then((result) => {
      res.json({
        data: result,
        msg: "Data successfully updated.",
      });
    })
    .catch((err) => {
      console.log(err);
    });
});

// Delete student
studentRoute.route("/delete-student/:id").delete(async (req, res) => {
  await Student.findByIdAndRemove(req.params.id)
    .then(() => {
      res.json({
        msg: "Data successfully updated.",
      });
    })
    .catch((err) => {
      console.log(err);
    });
});

module.exports = studentRoute;

Configure Mean Stack backend

Now we’ll create index.js file in backend folder’s root. Run the below command to generate backend > index.js file.

touch index.js

Mange Backend settings in Mean stack Project.

Now we are going to create index.js file this file will hold the core logic of our Mean stack project’s backend logic.

This file will manage the following things.

  • Setup port using express
  • Setup 404 error using express.js
  • Making mongoDB database connection
  • Serving static files using express js in Mean stack app
  • Handling errors using Express js in Angular Mean stack project
const express = require("express");
const path = require("path");
const mongoose = require("mongoose");
const cors = require("cors");
const bodyParser = require("body-parser");

// Connecting MongoDB
async function mongoDbConnection() {
  await mongoose.connect(
    "mongodb://127.0.0.1:27017/test",
    {
      useNewUrlParser: true,
      useUnifiedTopology: true,
    },
    6000
  );
}
mongoDbConnection().then(() => {
  console.log("MongoDB successfully connected.");
}),
  (err) => {
    console.log("Could not connected to database : " + err);
  };

// Set up express js port
const studentRoute = require("./routes/student.routes");

const app = express();
app.use(bodyParser.json());
app.use(
  bodyParser.urlencoded({
    extended: false,
  })
);
app.use(cors());

// Setting up static directory
app.use(
  express.static(path.join(__dirname, "dist/angular-material-mean-stack"))
);

// RESTful API root
app.use("/api", studentRoute);

// PORT
const port = process.env.PORT || 8000;

app.listen(port, () => {
  console.log("Connected to port " + port);
});

// Find 404 and hand over to error handler
app.use((req, res, next) => {
  next(createError(404));
});

// Index Route
app.get("/", (req, res) => {
  res.send("invaild endpoint");
});

app.get("*", (req, res) => {
  res.sendFile(
    path.join(__dirname, "dist/angular-material-mean-stack/index.html")
  );
});

// error handler
app.use(function (err, req, res, next) {
  console.error(err.message);
  if (!err.statusCode) err.statusCode = 500;
  res.status(err.statusCode).send(err.message);
});

Build Angular Service to Consume REST APIs

To create Mean stack student records management system app. We need to create a service file where we’ll consume REST APIs to manage the student data. This service file will manage the Create, Read, Update and Delete operations.

Configure Angular HttpClientModule:

Import HttpClientModule service in app.module.ts file.


import { HttpClientModule } from '@angular/common/http';

@NgModule({
  imports: [
    HttpClientModule
   ]
})

Create & configure Student class:

Enter the below command to create shared > student.ts file.

export class Student {
   _id: String;
   student_name: String;
   student_email: String;
   section: String;
   subjects: Array<string>;
   dob: Date;
   gender: String;
}

Create Angular Service to Consume REST APIs

Enter the following command to create Angular service to manage CRUD operations in MEAN Stack web app.

ng g s shared/api

In the given below code we’ve consumed REST APIs using Angular service. Add the following code in your shared > api.service.ts file.

import { Injectable } from '@angular/core';
import { Student } from './student';
import { Observable, throwError } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import {
  HttpClient,
  HttpHeaders,
  HttpErrorResponse,
} from '@angular/common/http';

@Injectable({
  providedIn: 'root',
})
export class ApiService {
  endpoint: string = 'http://localhost:8000/api';
  headers = new HttpHeaders().set('Content-Type', 'application/json');

  constructor(private http: HttpClient) {}

  // Add student
  AddStudent(data: Student): Observable<any> {
    let API_URL = `${this.endpoint}/add-student`;
    return this.http.post(API_URL, data).pipe(catchError(this.errorMgmt));
  }

  // Get all students
  GetStudents() {
    return this.http.get(`${this.endpoint}`);
  }

  // Get student
  GetStudent(id): Observable<any> {
    let API_URL = `${this.endpoint}/read-student/${id}`;
    return this.http.get(API_URL, { headers: this.headers }).pipe(
      map((res: Response) => {
        return res || {};
      }),
      catchError(this.errorMgmt)
    );
  }

  // Update student
  UpdateStudent(id, data): Observable<any> {
    let API_URL = `${this.endpoint}/update-student/${id}`;
    return this.http
      .put(API_URL, data, { headers: this.headers })
      .pipe(catchError(this.errorMgmt));
  }

  // Delete student
  DeleteStudent(id): Observable<any> {
    var API_URL = `${this.endpoint}/delete-student/${id}`;
    return this.http.delete(API_URL).pipe(catchError(this.errorMgmt));
  }

  // Error handling
  errorMgmt(error: HttpErrorResponse) {
    let errorMessage = '';
    if (error.error instanceof ErrorEvent) {
      // Get client-side error
      errorMessage = error.error.message;
    } else {
      // Get server-side error
      errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`;
    }
    console.log(errorMessage);
    return throwError(() => {
      return errorMessage;
    });
  }
}

Go to app.module.ts file and import this API service like given below.


import { ApiService } from './shared/api.service';

@NgModule({
  providers: [ApiService]
})

Add Student using MEAN Stack REST APIs with Angular Material

In this part of the tutorial we will learn to add student in the MongoDB database. We’ll be using Angular Reactive form to add student in the database.

Import ReactiveFormsModule API in App Module File

In order to work with Reactive Forms we must import the ReactiveFormsModule API and FormsModule API in app.module.ts file.


import { FormsModule, ReactiveFormsModule } from '@angular/forms';

@NgModule({
  imports: [
    ReactiveFormsModule,
    FormsModule
  ],
})

export class AppModule { }

Go to add-student.component.ts file and include the given below code.

import { Router } from '@angular/router';
import { Component, OnInit, ViewChild, NgZone } from '@angular/core';
import { COMMA, ENTER } from '@angular/cdk/keycodes';
import { MatChipInputEvent } from '@angular/material/chips';
import { ApiService } from './../../shared/api.service';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';

export interface Subject {
  name: string;
}

@Component({
  selector: 'app-add-student',
  templateUrl: './add-student.component.html',
  styleUrls: ['./add-student.component.scss'],
})
export class AddStudentComponent implements OnInit {
  visible = true;
  selectable = true;
  removable = true;
  addOnBlur = true;
  @ViewChild('chipList') chipList;
  @ViewChild('resetStudentForm') myNgForm;
  readonly separatorKeysCodes: number[] = [ENTER, COMMA];
  studentForm: FormGroup;
  subjectArray: Subject[] = [];
  SectioinArray: any = ['A', 'B', 'C', 'D', 'E'];

  ngOnInit() {
    this.submitBookForm();
  }

  constructor(
    public fb: FormBuilder,
    private router: Router,
    private ngZone: NgZone,
    private studentApi: ApiService
  ) {}

  /* Reactive book form */
  submitBookForm() {
    this.studentForm = this.fb.group({
      student_name: ['', [Validators.required]],
      student_email: ['', [Validators.required]],
      section: ['', [Validators.required]],
      subjects: [this.subjectArray],
      dob: ['', [Validators.required]],
      gender: ['Male'],
    });
  }

  /* Add dynamic languages */
  add(event: MatChipInputEvent): void {
    let input = (event.value || '').trim();
    let value = event.value;
    // Add language
    if ((value || '').trim() && this.subjectArray.length < 5) {
      this.subjectArray.push({ name: value.trim() });
    }
    // Reset the input value
    event.chipInput!.clear();
  }

  /* Remove dynamic languages */
  remove(subject: Subject): void {
    const index = this.subjectArray.indexOf(subject);
    if (index >= 0) {
      this.subjectArray.splice(index, 1);
    }
  }

  /* Date */
  formatDate(e) {
    var convertDate = new Date(e.target.value).toISOString().substring(0, 10);
    this.studentForm.get('dob').setValue(convertDate, {
      onlyself: true,
    });
  }

  /* Get errors */
  public handleError = (controlName: string, errorName: string) => {
    return this.studentForm.controls[controlName].hasError(errorName);
  };

  /* Submit book */
  submitStudentForm() {
    if (this.studentForm.valid) {
      this.studentApi.AddStudent(this.studentForm.value).subscribe((res) => {
        this.ngZone.run(() => this.router.navigateByUrl('/students-list'));
      });
    }
  }
}

Then go to add-student.component.html file and add the following code.

<!-- Title group  -->
<div class="title-group">
  <h1 class="mat-h1">Add Student</h1>
  <mat-divider fxFlex="1 0"></mat-divider>
</div>

<!-- Form -->
<div class="inner-wrapper">
  <form
    [formGroup]="studentForm"
    (ngSubmit)="submitStudentForm()"
    #resetStudentForm="ngForm"
    novalidate
  >
    <!-- Left block -->
    <mat-card>
      <div class="controlers-wrapper">
        <!-- Name -->
        <mat-form-field class="example-full-width">
          <input
            matInput
            placeholder="Student name"
            formControlName="student_name"
          />
          <mat-error *ngIf="handleError('student_name', 'required')">
            You must provide a <strong>student name</strong>
          </mat-error>
        </mat-form-field>

        <!-- Email -->
        <mat-form-field class="example-full-width">
          <input
            matInput
            placeholder="Student email"
            formControlName="student_email"
          />
          <mat-error *ngIf="handleError('student_email', 'required')">
            You must provide a<strong>student email</strong>
          </mat-error>
        </mat-form-field>

        <!-- Section -->
        <mat-form-field>
          <mat-label>Section</mat-label>
          <mat-select formControlName="section">
            <mat-option
              [value]="sectioinArray"
              *ngFor="let sectioinArray of SectioinArray"
              >{{ sectioinArray }}
            </mat-option>
          </mat-select>
          <mat-error *ngIf="handleError('section', 'required')">
            Section is required
          </mat-error>
        </mat-form-field>
      </div>
    </mat-card>

    <!-- Right block -->
    <mat-card>
      <div class="controlers-wrapper">
        <!-- Add subjects -->
        <mat-form-field class="multiple-items">
          <mat-chip-grid #chipGrid>
            <mat-chip
              *ngFor="let subjectArray of subjectArray"
              [selectable]="selectable"
              [removable]="removable"
              (removed)="remove(subjectArray)"
            >
              {{ subjectArray.name }}
              <mat-icon matChipRemove *ngIf="removable">cancel</mat-icon>
            </mat-chip>
            <input
              matInput
              placeholder="Add subject"
              [matChipInputFor]="chipGrid"
              [matChipInputSeparatorKeyCodes]="separatorKeysCodes"
              [matChipInputAddOnBlur]="addOnBlur"
              (matChipInputTokenEnd)="add($event)"
            />
          </mat-chip-grid>
          <i
            class="material-icons tooltip-info"
            matTooltip="Enter subject name and press enter to add subjects"
          >
            info
          </i>
        </mat-form-field>

        <!-- Date of birth -->
        <mat-form-field>
          <input
            matInput
            readonly
            [matDatepicker]="picker"
            placeholder="Date of birth"
            formControlName="dob"
            (dateChange)="formatDate($event)"
          />
          <mat-datepicker-toggle
            matSuffix
            [for]="picker"
          ></mat-datepicker-toggle>
          <mat-datepicker #picker></mat-datepicker>
          <mat-error *ngIf="handleError('dob', 'required')">
            Date of birth is required
          </mat-error>
        </mat-form-field>

        <!-- Gender -->
        <div class="misc-bottom-padding">
          <mat-label>Gender:</mat-label>
          <mat-radio-group
            aria-label="Select an option"
            formControlName="gender"
          >
            <mat-radio-button value="Male">Male</mat-radio-button>
            <mat-radio-button value="Female">Female</mat-radio-button>
          </mat-radio-group>
        </div>
      </div>
    </mat-card>

    <!-- Submit & Reset -->
    <mat-card>
      <div class="full-wrapper button-wrapper">
        <div class="button-wrapper">
          <button mat-flat-button color="warn">Submit</button>
        </div>
      </div>
    </mat-card>
  </form>
</div>

Show Students List and Delete Student Object

Go to students-list.component.ts file and add the given below code. In this file, we’ll manage the following tasks.

  • Implement the Angular material data tables and Pagination with Mean stack project.
  • Render Students List using Mean stack REST APIs
  • Delete Single Object using REST APIs in Mean stack app
import { Student } from './../../shared/student';
import { ApiService } from './../../shared/api.service';
import { Component, ViewChild, OnInit } from '@angular/core';
import { MatPaginator } from '@angular/material/paginator';
import { MatTableDataSource } from '@angular/material/table';


@Component({
  selector: 'app-students-list',
  templateUrl: './students-list.component.html',
  styleUrls: ['./students-list.component.scss'],
})
export class StudentsListComponent implements OnInit {
  StudentData: any = [];
  dataSource: MatTableDataSource<Student>;
  @ViewChild(MatPaginator) paginator: MatPaginator;
  displayedColumns: string[] = [
    '_id',
    'student_name',
    'student_email',
    'section',
    'action',
  ];

  constructor(private studentApi: ApiService) {
    this.studentApi.GetStudents().subscribe((data) => {
      this.StudentData = data;
      this.dataSource = new MatTableDataSource<Student>(this.StudentData);
      setTimeout(() => {
        this.dataSource.paginator = this.paginator;
      }, 0);
    });
  }

  ngOnInit() {}

  deleteStudent(index: number, e) {
    if (window.confirm('Are you sure')) {
      const data = this.dataSource.data;
      data.splice(
        this.paginator.pageIndex * this.paginator.pageSize + index,
        1
      );
      this.dataSource.data = data;
      this.studentApi.DeleteStudent(e._id).subscribe();
    }
  }
}

Now, go to students-list.component.html file and include the following code.

<!-- Title group  -->
<div class="title-group">
  <h1 class="mat-h1">Students List</h1>
  <mat-divider fxFlex="1 0"></mat-divider>
</div>

<p *ngIf="StudentData.length <= 0" class="no-data">
  There is no student added yet!
</p>

<div class="container" *ngIf="StudentData.length > 0">
  <div class="mat-elevation-z8">
    <table mat-table [dataSource]="dataSource">
      <ng-container matColumnDef="_id">
        <th mat-header-cell *matHeaderCellDef>Student ID</th>
        <td mat-cell *matCellDef="let element">{{ element._id }}</td>
      </ng-container>

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

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

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

      <ng-container matColumnDef="action">
        <th mat-header-cell *matHeaderCellDef>Action</th>
        <td mat-cell *matCellDef="let element; let i = index">
          <button
            mat-raised-button
            color="primary"
            class="push-right"
            [routerLink]="['/edit-student/', element._id]"
          >
            Edit
          </button>
          <button
            mat-raised-button
            color="accent"
            (click)="deleteStudent(i, element)"
          >
            Delete
          </button>
        </td>
      </ng-container>

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

    <mat-paginator
      [pageSizeOptions]="[5, 10, 20]"
      showFirstLastButtons
    ></mat-paginator>
  </div>
</div>

Edit Students Object in Mean Stack App

We are going to create edit functionality using RESTful API in Mean stack app with Angular Material.

Go to edit-student.component.ts file and add the following code.

import { Router, ActivatedRoute } from '@angular/router';
import { Component, OnInit, ViewChild, NgZone } from '@angular/core';
import { COMMA, ENTER } from '@angular/cdk/keycodes';
import { MatChipInputEvent } from '@angular/material/chips';
import { ApiService } from './../../shared/api.service';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';

export interface Subject {
  name: string;
}

@Component({
  selector: 'app-edit-student',
  templateUrl: './edit-student.component.html',
  styleUrls: ['./edit-student.component.scss'],
})
export class EditStudentComponent implements OnInit {
  visible = true;
  selectable = true;
  removable = true;
  addOnBlur = true;
  @ViewChild('chipList') chipList;
  @ViewChild('resetStudentForm') myNgForm;
  readonly separatorKeysCodes: number[] = [ENTER, COMMA];
  studentForm: FormGroup;
  subjectArray: Subject[] = [];
  SectioinArray: any = ['A', 'B', 'C', 'D', 'E'];

  ngOnInit() {
    this.updateBookForm();
  }

  constructor(
    public fb: FormBuilder,
    private router: Router,
    private ngZone: NgZone,
    private actRoute: ActivatedRoute,
    private studentApi: ApiService
  ) {
    var id = this.actRoute.snapshot.paramMap.get('id');
    this.studentApi.GetStudent(id).subscribe((data) => {
      this.subjectArray = data.data.subjects;
      this.studentForm = this.fb.group({
        student_name: [data.data.student_name, [Validators.required]],
        student_email: [data.data.student_email, [Validators.required]],
        section: [data.data.section, [Validators.required]],
        subjects: [data.data.subjects],
        dob: [data.data.dob, [Validators.required]],
        gender: [data.gender],
      });
    });
  }

  /* Reactive book form */
  updateBookForm() {
    this.studentForm = this.fb.group({
      student_name: ['', [Validators.required]],
      student_email: ['', [Validators.required]],
      section: ['', [Validators.required]],
      subjects: [this.subjectArray],
      dob: ['', [Validators.required]],
      gender: ['Male'],
    });
  }

  /* Add dynamic languages */
  add(event: MatChipInputEvent): void {
    let input = (event.value || '').trim();
    let value = event.value;
    // Add language
    if ((value || '').trim() && this.subjectArray.length < 5) {
      this.subjectArray.push({ name: value.trim() });
    }
    // Reset the input value
    if (input) {
      event.chipInput!.clear();
    }
  }

  /* Remove dynamic languages */
  remove(subject: Subject): void {
    const index = this.subjectArray.indexOf(subject);
    if (index >= 0) {
      this.subjectArray.splice(index, 1);
    }
  }

  /* Date */
  formatDate(e) {
    var convertDate = new Date(e.target.value).toISOString().substring(0, 10);
    this.studentForm.get('dob').setValue(convertDate, {
      onlyself: true,
    });
  }

  /* Get errors */
  public handleError = (controlName: string, errorName: string) => {
    return this.studentForm.controls[controlName].hasError(errorName);
  };

  /* Update book */
  updateStudentForm() {
    console.log(this.studentForm.value);
    var id = this.actRoute.snapshot.paramMap.get('id');
    if (window.confirm('Are you sure you want to update?')) {
      this.studentApi
        .UpdateStudent(id, this.studentForm.value)
        .subscribe((res) => {
          this.ngZone.run(() => this.router.navigateByUrl('/students-list'));
        });
    }
  }
}

Now go to edit-student.component.html file and add the following code.

<!-- Title group  -->
<div class="title-group">
  <h1 class="mat-h1">Add Student</h1>
  <mat-divider fxFlex="1 0"></mat-divider>
</div>

<!-- Form -->
<div class="inner-wrapper">
  <form
    [formGroup]="studentForm"
    (ngSubmit)="updateStudentForm()"
    #resetStudentForm="ngForm"
    novalidate
  >
    <!-- Left block -->
    <mat-card>
      <div class="controlers-wrapper">
        <!-- Name -->
        <mat-form-field class="example-full-width">
          <input
            matInput
            placeholder="Student name"
            formControlName="student_name"
          />
          <mat-error *ngIf="handleError('student_name', 'required')">
            You must provide a<strong>student name</strong>
          </mat-error>
        </mat-form-field>

        <!-- Email -->
        <mat-form-field class="example-full-width">
          <input
            matInput
            placeholder="Student email"
            formControlName="student_email"
          />
          <mat-error *ngIf="handleError('student_email', 'required')">
            You must provide a<strong>student email</strong>
          </mat-error>
        </mat-form-field>

        <!-- Section -->
        <mat-form-field>
          <mat-label>Section</mat-label>
          <mat-select formControlName="section">
            <mat-option
              [value]="sectioinArray"
              *ngFor="let sectioinArray of SectioinArray"
              >{{ sectioinArray }}
            </mat-option>
          </mat-select>
          <mat-error *ngIf="handleError('section', 'required')">
            Section is required
          </mat-error>
        </mat-form-field>
      </div>
    </mat-card>

    <!-- Right block -->
    <mat-card>
      <div class="controlers-wrapper">
        <!-- Add subjects -->
        <mat-form-field class="multiple-items">
          <mat-chip-grid #chipGrid>
            <mat-chip
              *ngFor="let subjectArray of subjectArray"
              [selectable]="selectable"
              [removable]="removable"
              (removed)="remove(subjectArray)"
            >
              {{ subjectArray.name }}
              <mat-icon matChipRemove *ngIf="removable">cancel</mat-icon>
            </mat-chip>
            <input
              placeholder="Add subject"
              [matChipInputFor]="chipGrid"
              [matChipInputSeparatorKeyCodes]="separatorKeysCodes"
              [matChipInputAddOnBlur]="addOnBlur"
              (matChipInputTokenEnd)="add($event)"
            />
          </mat-chip-grid>
          <i
            class="material-icons tooltip-info"
            matTooltip="Enter subject name and press enter to add subjects"
          >
            info
          </i>
        </mat-form-field>

        <!-- Date of birth -->
        <mat-form-field>
          <input
            matInput
            readonly
            [matDatepicker]="picker"
            placeholder="Date of birth"
            formControlName="dob"
            (dateChange)="formatDate($event)"
          />
          <mat-datepicker-toggle
            matSuffix
            [for]="picker"
          ></mat-datepicker-toggle>
          <mat-datepicker #picker></mat-datepicker>
          <mat-error *ngIf="handleError('dob', 'required')">
            Date of birth is required
          </mat-error>
        </mat-form-field>

        <!-- Gender -->
        <div class="misc-bottom-padding">
          <mat-label>Gender:</mat-label>
          <mat-radio-group
            aria-label="Select an option"
            formControlName="gender"
          >
            <mat-radio-button value="Male">Male</mat-radio-button>
            <mat-radio-button value="Female">Female</mat-radio-button>
          </mat-radio-group>
        </div>
      </div>
    </mat-card>

    <!-- Submit & Reset -->
    <mat-card>
      <div class="full-wrapper button-wrapper">
        <div class="button-wrapper">
          <button mat-flat-button color="warn">Update</button>
        </div>
      </div>
    </mat-card>
  </form>
</div>

Start The Mean Stack App

Run the set of commands to start nodemon, MongoDB and Angular app to test the project.

Start the Angular project:

ng serve --open

Start Node Server:

Make sure to setup and follow the guide to start the MongoDB community and MongoDB Compass GUI database in your local system.

Start the nodemon server:

cd backend && npx nodemon server

Also, pass the MongoDB database URL to the mongoose.connect(‘…’) method in the backend/index.js file.

Angular frontend URL:
http://localhost:4200

MEAN stack backend URL:
http://localhost:8000/api

MEAN stack RESTful APIs using Express JS

RESTful APIs Method API URL
GET /api
POST /add-student
GET /read-student/id
PUT /update-student/id
DELETE /delete-student/id

We can hit the below command in the terminal to check out how our newly created RESTful APIs are working.

curl -i -H "Accept: application/json" localhost:8000/api

# HTTP/1.1 200 OK
# X-Powered-By: Express
# Access-Control-Allow-Origin: *
# Content-Type: application/json; charset=utf-8
# Content-Length: 58
# ETag: W/"3a-dzxOuKmgt3HAevjaPlycYSK+FhI"
# Date: Sun, 26 May 2019 18:53:03 GMT
# Connection: keep-alive

If we are getting this type of response that means we are ready to go with our APIs. Or similarly we can also use Postmen API development environment tool to test our RESTful APIs.

Conclusion

Finally, we have created a basic Angular MEAN stack CRUD web app with Angular Material. We’ve focused on every important topic in this blog.

Anyhow, if we have missed anything you can check out GitHub repo of this project.