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.
Table of Contents
- Workflow of MEAN Stack Angular Material tutorial
- Installing Node JS and Angular CLI
- Angular Project setup
- Setting up Angular Routes to navigate between components.
- Setting up Angular Material UI Library in Angular project.
- Build Mean Stack Backend with MongoDB, Node JS and Express JS.
- Build Service to Consume REST APIs.
- Add Student using MEAN Stack REST APIs with Angular Material.
- Show Students List and Delete Student Object.
- Edit Students Object
- Start The Mean Stack App
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.