Hi there,
Angular is one of the most popular Javascript frameworks out there. On the other hand, Material UI is a library that provides us with a large collection of ready-made components such as buttons, toolbars, icons, inputs, etc. Material UI will make our development process super fast.
Building a CRUD app with Angular is a great way to familiarize yourself with its syntaxes and features.
You can grab the full source code from here.
Now, let’s start building this simple employee management app, where you can add, edit, and delete employees.
If you want to follow along, you have to be familiar with the following tools:
Angular 15 and its core concepts, like components, templates, services, and modules.
Material UI
NodeJS and Typescript
Visual Studio Code (optional)
Angular CLI and JSON Server.
To check if NodeJS and Angular CLI are already installed on your machine type in the following commands in your terminal or the integrated terminal of VS Code:
node -v
ng version
If these tools are not installed then this will give an error otherwise this will return the version number. To install NodeJS go to https://nodejs.org/en/download/ and if you install NodeJS, npm will also get installed along with it.
You can install Angular CLI via the following command:
npm install -g @angular/cli
Now at last let’s install JSON-Server via the following command:
npm install -g json-server
Now we’re ready to set up our project!
To set up our Angular project, open your terminal and run the following command:
ng new simple-employee-management-crud-app
When using Angular CLI, it prompts you to select certain settings, such as whether to use CSS or SCSS for styling, among other options. In this tutorial, we've opted for the default settings. After making your selections, Angular CLI will begin the project creation process, setting up the project with default configurations and providing some boilerplate code to kickstart your work. The directory structure will resemble the following image, inclusive of all necessary helper libraries:
At this stage, let’s install the Material UI library for our app via the following command:
ng add @angular/material
This time again you will be asked to choose some settings. Again all the default options were chosen.
Now, let’s navigate into the project directory by typing the following command in the terminal:
cd simple-employee-management-crud-app
Before starting to write code for our app let’s see if this app is working perfectly by typing the following command:
ng serve -o
This command will redirect you to http://localhost:4200 and you will see a web application like the following image:
Now we’re ready to create the components, services, modules, etc. needed for this simple CRUD app.
Our employee management crud app will look like the following image:
When the ADD EMPLOYEE button is clicked a dialog will show up with a form to add a new employee like the following image:
We will be able to filter, sort, edit, delete an employee, and use pagination. And we’re going to create the table, buttons, paginations, and filters using the Material UI library.
Now let’s start coding step-by-step:
Create a db.json
file in the project root directory, not inside the src/
or app/
directory. Now open the db.json
file and paste the following code inside:
{
"employees": [
{
"id": 1,
"firstName": "Brinn",
"lastName": "Jephcote",
"email": "[email protected]",
"dob": "1981-10-05T12:09:39Z",
"gender": "Female",
"education": "Graduate",
"company": "Gabspot",
"experience": 36,
"salary": 37
},
{
"id": 2,
"firstName": "Kenneth",
"lastName": "MacElholm",
"email": "[email protected]",
"dob": "1991-09-12T22:14:02Z",
"gender": "Male",
"education": "Matric",
"company": "Agivu",
"experience": 75,
"salary": 17
}
]
}
Here we’re just storing some employee details. This db.json
file is our database and the employees array our table!
Now open another terminal tab or split the terminal in VSCode then start the json-server
via the following command:
json-server --watch 'db.json'
This command will locally start a server at port 3000 where we will send our requests for creating, reading, updating, and deleting an employee.
In Angular, services are used to manage data and state that is shared between components. We will create a service for managing employee data in our app.
To create the employee service, run the following command:
ng generate service employee –skip-tests
This command will create a boilerplate service file for us inside the src/app
directory called employee.service.ts
. Next, let's implement the logic for getting, adding, updating, and deleting employees in the employee service.
Open the employee.service.ts
file and paste the following code:
import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class EmployeeService {
baseUrl: string = "http://localhost:3000/";
constructor(private httpClient: HttpClient) { }
addEmployee(data: any): Observable<any> {
return this.httpClient.post(this.baseUrl + 'employees', data);
}
updateEmployee(id: number, data: any): Observable<any> {
return this.httpClient.put(this.baseUrl + employees / ${ id }, data);
}
getEmployeeList(): Observable<any> {
return this.httpClient.get(this.baseUrl + 'employees');
}
deleteEmployee(id: number): Observable<any> {
return this.httpClient.delete(this.baseUrl + employees / ${ id });
}
}
Here, we’ve created four methods for getting an employee list, adding an employee, updating an employee, and deleting an employee respectively by using the built-in HttpClient service for sending requests to our JSON-server.
Now that our service file is also complete let’s build the components necessary for this app.
In Angular, a component is a building block of the application. It is responsible for managing a piece of the UI, handling user interactions, and rendering data. We will create one additional component in our app: emp-add-edit
component. This component will be responsible for adding or updating an employee.
To create this component, run the following command in the terminal:
ng generate component emp-add-edit –skip-tests
This command will create a directory called emp-add-edit
inside our /src/app
folder and some other necessary files with some boilerplate code for you. We will use this component as the dialog(modal) to create and update an employee. So for now let’s keep it like this and move on to make the toolbar and employee table inside the app component. To do so, open up the app.component.html
file and paste the following code inside it.
<!-- The toolbar of our app -->
<mat-toolbar color="primary">
<span>Employee Management Crud App</span>
<span class="example-spacer"></span>
<button mat-raised-button color="accent" (click)="openAddEditEmployeeDialog()">ADD EMPLOYEE</button>
</mat-toolbar>
<!-- The body of our app -->
<div class="main-body">
<!-- The filter section -->
<mat-form-field aria-haspopup="outline">
<mat-label>Filter</mat-label>
<input matInput (keyup)="applyFilter($event)" placeholder="i.e David Smith" #input>
</mat-form-field>
<!-- The employee details table -->
<div class="table-container">
<table mat-table [dataSource]="dataSource" matSort>
<ng-container matColumnDef="id">
<th mat-header-cell *matHeaderCellDef mat-sort-header> ID </th>
<td mat-cell *matCellDef="let row"> {{row.id}} </td>
</ng-container>
<ng-container matColumnDef="firstName">
<th mat-header-cell *matHeaderCellDef mat-sort-header> First Name </th>
<td mat-cell *matCellDef="let row"> {{row.firstName}}</td>
</ng-container>
<ng-container matColumnDef="lastName">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Last Name </th>
<td mat-cell *matCellDef="let row"> {{row.lastName}}</td>
</ng-container>
<ng-container matColumnDef="email">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Email </th>
<td mat-cell *matCellDef="let row"> {{row.email}}</td>
</ng-container>
<ng-container matColumnDef="dob">
<th mat-header-cell *matHeaderCellDef mat-sort-header> DOB </th>
<td mat-cell *matCellDef="let row"> {{row.dob | date}}</td>
</ng-container>
<ng-container matColumnDef="gender">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Gender </th>
<td mat-cell *matCellDef="let row"> {{row.gender}}</td>
</ng-container>
<ng-container matColumnDef="education">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Education </th>
<td mat-cell *matCellDef="let row"> {{row.education}}</td>
</ng-container>
<ng-container matColumnDef="company">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Company </th>
<td mat-cell *matCellDef="let row"> {{row.company}}</td>
</ng-container>
<ng-container matColumnDef="experience">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Experience </th>
<td mat-cell *matCellDef="let row"> {{row.experience}}</td>
</ng-container>
<ng-container matColumnDef="salary">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Salary </th>
<td mat-cell *matCellDef="let row"> {{row.salary | currency:'USD'}}L</td>
</ng-container>
<ng-container matColumnDef="action">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Action </th>
<td mat-cell *matCellDef="let row">
<span (click)="openEditForm(row)" class="action-icon" style="margin-right: 5px;">
<mat-icon color="primary">edit</mat-icon>
</span>
<span (click)="deleteEmployee(row.id)" class="action-icon">
<mat-icon color="warn">delete</mat-icon>
</span>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
<!-- This row will be shown when there is no matching data. -->
<tr class="mat-row" *matNoDataRow>
<td class="mat-cell" colspan="4">No data matching the filter "{{input.value}}"</td>
</tr>
</table>
<mat-paginator [pageSizeOptions]="[5, 10, 25, 100]" aria-label="Select page of users"></mat-paginator>
</div>
</div>
Well, nothing complicated!
In the first section, we’re creating the toolbar of our app, and then in the second section, we’re creating the table for the list of employees and also creating pagination. We’re creating these using the Material UI library (if you notice all those custom tags, and attributes starting with “mat”). In the toolbar, we created a button for adding an employee and bound a click event to this button. In the click event, we’re passing a method openAddEditEmployeeDialog()
.
This function will be used to invoke and open our dialog (emp-add-edit
component). So now let’s paste the following CSS code in the app.component.css
file to make our toolbar more beautiful:
.example-spacer {
flex: 1 1 auto;
}
mat-form-field {
width: 100%;
margin: auto;
}
.main-body {
max-width: 80%;
margin: 20px auto;
}
.action-icon:hover {
opacity: 0.8;
cursor: pointer;
}
Now it’s time to write the logic for this component.
So let’s open the app.component.ts
file and paste the following code:
import { Component, OnInit, ViewChild } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { EmpAddEditComponent } from './emp-add-edit/emp-add-edit.component';
import { EmployeeService } from './employee.service';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit {
// the columns that will be displayed in the employee details table
displayedColumns: string[] = [
'id',
'firstName',
'lastName',
'email',
'dob',
'gender',
'education',
'company',
'experience',
'salary',
'action',
];
// employee list will be assigned to this and it is passed as the data source to the mat-table in the HTML template
dataSource!: MatTableDataSource<any>;
@ViewChild(MatPaginator) paginator!: MatPaginator;
@ViewChild(MatSort) sort!: MatSort;
// dependency injection
constructor(
private dialog: MatDialog,
private empService: EmployeeService,
) {}
ngOnInit(): void {
this.getEmployeeList();
}
openAddEditEmployeeDialog() {
const dialogRef = this.dialog.open(EmpAddEditComponent);
dialogRef.afterClosed().subscribe({
next: (val) => {
if (val) {
this.getEmployeeList();
}
},
});
}
getEmployeeList() {
this.empService.getEmployeeList().subscribe({
next: (res) => {
this.dataSource = new MatTableDataSource(res);
this.dataSource.sort = this.sort;
this.dataSource.paginator = this.paginator;
console.log(res);
},
error: (err) => {
console.log(err);
},
});
}
// for searching employees with firstname, lastname, gennder, etc
applyFilter(event: Event) {
const filterValue = (event.target as HTMLInputElement).value;
this.dataSource.filter = filterValue.trim().toLowerCase();
if (this.dataSource.paginator) {
this.dataSource.paginator.firstPage();
}
}
deleteEmployee(id: number) {
let confirm = window.confirm("Are you sure you want to delete this employee?");
if(confirm) {
this.empService.deleteEmployee(id).subscribe({
next: (res) => {
alert('Employee deleted!');
this.getEmployeeList();
},
error: (err) => {
console.log(err);
},
});
}
}
openEditForm(data: any) {
const dialogRef = this.dialog.open(EmpAddEditComponent, {
data,
});
dialogRef.afterClosed().subscribe({
next: (val) => {
if (val) {
this.getEmployeeList();
}
}
});
}
}
At first, we’re importing the components we used in our app.component.html from the Material UI library. Then we’re initiating some properties like displayedColumns
and dataSource
for the mat-table
component. And at last, we’ve created some methods like openAddEditEmployeeDialog()
, getEmployeeList()
, applyFilter()
, deleteEmployee()
, openEditForm()
. Some of these methods we used in the app.component.html
file to open the dialog or delete an employee or update an employee or filter the table.
Now that our app component is complete if you try to view the app you will get a bunch of dirty errors. And why is that? Because we still didn’t import all those modules we used from the material-ui library in the app.module.ts
. So, now let’s open app.module.ts
and paste the following code:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatIconModule } from '@angular/material/icon';
import { MatButtonModule } from '@angular/material/button';
import { EmpAddEditComponent } from './emp-add-edit/emp-add-edit.component';
import { MatDialogModule } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatNativeDateModule } from '@angular/material/core';
import { MatRadioModule } from '@angular/material/radio';
import { MatSelectModule } from '@angular/material/select';
import { ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { MatTableModule } from '@angular/material/table';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatSortModule } from '@angular/material/sort';
@NgModule({
declarations: [
AppComponent,
EmpAddEditComponent,
],
imports: [
BrowserModule,
AppRoutingModule,
BrowserAnimationsModule,
MatToolbarModule,
MatIconModule,
MatButtonModule,
MatDialogModule,
MatFormFieldModule,
MatInputModule,
MatDatepickerModule,
MatNativeDateModule,
MatRadioModule,
MatSelectModule,
ReactiveFormsModule,
HttpClientModule,
MatTableModule,
MatPaginatorModule,
MatSortModule,
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Now that our app component is complete, it's time to prepare the emp-add-edit
component. We will first prepare the emp-add-edit.component.html
.
Open emp-add-edit.component.html
file and paste the following code:
<div mat-dialog-title>
<h1>{{data ? 'Edit': 'Add'}} Employee Form</h1>
</div>
<form [formGroup]="empForm" (ngSubmit)="onSubmit()">
<div mat-dialog-content class="content">
<div class="row">
<mat-form-field appearance="outline">
<mat-label>First name:</mat-label>
<input matInput type="text" placeholder="i.e David" formControlName="firstName">
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Last Name:</mat-label>
<input matInput placeholder="i.e Smith" formControlName="lastName">
</mat-form-field>
</div>
<div class="row">
<mat-form-field appearance="outline">
<mat-label>Email:</mat-label>
<input matInput type="email" placeholder="i.e [email protected]" formControlName="email">
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Date of birth:</mat-label>
<input matInput [matDatepicker]="picker" formControlName="dob">
<mat-hint>MM/DD/YYYY</mat-hint>
<mat-datepicker-toggle matIconSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker #picker></mat-datepicker>
</mat-form-field>
</div>
<div class="row">
<mat-radio-group aria-label="Select an option" formControlName="gender">
<mat-label><b>Gender:</b></mat-label>
<mat-radio-button value="male">Male</mat-radio-button>
<mat-radio-button value="female">Female</mat-radio-button>
<mat-radio-button value="others">Others</mat-radio-button>
<mat-radio-button value="others">Not Interested</mat-radio-button>
</mat-radio-group>
</div>
<div class="row">
<mat-form-field appearance="outline">
<mat-label>Education:</mat-label>
<mat-select formControlName="education">
<mat-option *ngFor="let val of education" [value]="val">{{val}}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Company:</mat-label>
<input matInput placeholder="i.e Amazon" formControlName="company">
</mat-form-field>
</div>
<div class="row">
<mat-form-field appearance="outline">
<mat-label>Experience:</mat-label>
<input matInput placeholder="i.e 5" type="number" formControlName="experience">
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Salary:</mat-label>
<input matInput placeholder="i.e 15" type="number" formControlName="salary">
<mat-hint>In dollar</mat-hint>
</mat-form-field>
</div>
</div>
<div mat-dialog-actions class="action-btns">
<button style="margin: 10px;" mat-raised-button color="accent" type="submit">{{data ? 'Update': 'Save'}}</button>
<button mat-raised-button type="button" color="warn" [mat-dialog-close]="false">Cancel</button>
</div>
</form>
Here we’re creating the dialog for adding and editing an employee.
We’ve created the input field such as firstname, lastname, gender, and etc for adding a new employee to our list. If you click on the edit button in the table then this dialog will open with the previous value and change the update button the new value will be saved. You can also cancel editing.
Now to make our dialog look more awesome paste the following code in the emp-add-edit.component.css file:
.row {
display: flex;
justify-content: space-between;
gap: 20px;
margin-bottom: 10px;
}
mat-form-field {
width: 100%;
}
mat-hint {
margin: 5px;
}
.action-btns {
float: right;
margin-right: 10px;
}
.action-btns button {
padding: 20px;
}
Now let’s add the logic of creating and updating an employee in the emp-add-edit.component.ts
via pasting the following code:
import { Component, Inject, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { EmployeeService } from '../employee.service';
@Component({
selector: 'app-emp-add-edit',
templateUrl: './emp-add-edit.component.html',
styleUrls: ['./emp-add-edit.component.css'],
})
export class EmpAddEditComponent implements OnInit {
empForm: FormGroup;
education: string[] = [
'Matric',
'Diploma',
'Intermediate',
'Graduate',
'Post Graduate',
];
constructor(
private empService: EmployeeService,
private dialogRef: MatDialogRef<EmpAddEditComponent>,
private formBuilder: FormBuilder,
@Inject(MAT_DIALOG_DATA) public data: any,
) {
this.empForm = this.formBuilder.group({
firstName: ['', Validators.required],
lastName: ['', Validators.required],
email: ['', Validators.required],
dob: ['', Validators.required],
gender: ['', Validators.required],
education: ['', Validators.required],
company: ['', Validators.required],
experience: ['', Validators.required],
salary: ['', Validators.required]
});
}
ngOnInit(): void {
this.empForm.patchValue(this.data);
}
onSubmit() {
if (this.empForm.valid) {
if (this.data) {
this.empService
.updateEmployee(this.data.id, this.empForm.value)
.subscribe({
next: (val: any) => {
alert('Employee details updated!');
this.dialogRef.close(true);
},
error: (err: any) => {
console.error(err);
alert("Error while updating the employee!");
},
});
} else {
this.empService.addEmployee(this.empForm.value).subscribe({
next: (val: any) => {
alert('Employee added successfully!');
this.empForm.reset();
this.dialogRef.close(true);
},
error: (err: any) => {
console.error(err);
alert("Error while adding the employee!");
},
});
}
}
}
}
Here, we’re collecting the input values using the FormBuilder modules and making sure that the required inputs are given using the Validators module. When the user clicks on the update or save button (if you’ve noticed we used conditional rendering in the HTML template to show update or save depending on the data property) an existing employee will update or a new employee will be added to our table.
Finally, our crud app is ready to be explored! So visit http://localhost:4200 and try to create, update, and delete an employee.
If you want to get the full source code of this simple employee management crud app feel free to download it from my GitLab repo here. Additionally, visit https://material.angular.io to get the code you need to use mat-table or mat-paginator or etc.
Building a CRUD app using Angular is very straightforward and fun. Breaking down the app into smaller components and services makes it a lot easier to manage the code and add new features easily in the future. Angular provides a lot of built-in functionality right out of the box that helps you to implement CRUD operations in a few lines of code. You can integrate this app with a real database without changing the existing source code.
I hope this tutorial has helped introduce you to Angular, Material UI, and JSON-Server. Good luck with your next Angular project!
Feel free to reach out by commenting if you find any errors!