Spring Boot 3 + Angular 15 + Material - Full Stack CRUD Application Example
In this section we will develop a full-stack application that is a basic Student management application using Spring Boot 3, Angular 15, Material and PostgreSQL. You could download the source code from our GitHub repository, the download link is provided at the end of this article.
After completing this tutorial what we will build?
We will build a full-stack web application that is a basic Student Management Application with CRUD features:
- Create Student
- List Student
- Update Student
- Delete Student
Technologies used:
- Spring Boot 3+
- Spring Data JPA
- PostgreSQL
- Java 17
- Angular 15
- Angular Material UI
1. Backend Development
Creating a Spring boot web application
First, open the Spring initializr: https://start.spring.io/
Then, Provide the Group and Artifact name. We have provided Group name com.knf.dev.demo and Artifact spring-boot3-postgresql-crud-app. Here I selected the Maven project - language Java 17, Spring Boot 3.1.4 (latest) and add Spring web dependency, PostgreSQL Driver and Spring Data JPA.
Then, click on the Generate button. When we click on the Generate button, it starts packing the project in a .zip(spring-boot3-postgresql-crud-app) file and downloads the project. Then, Extract the Zip file.
Then, import the project on your favourite IDE.
Final Project directory:
pom.xml
POM is an XML file that contains information about the project and configuration details utilized by Maven to build the project. It contains default values for most projects. More Info
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.knf.dev.demo</groupId>
<artifactId>spring-boot3-postgresql-crud-app</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot3-postgresql-crud-app</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.properties
spring:
datasource:
url: jdbc:postgresql://localhost:5432/mydb
username: postgres
password: root
jpa:
hibernate:
ddl-auto: update # Hibernate ddl auto (create, create-drop, validate, update),production set to none or comment it
show-sql: true
database-platform: org.hibernate.dialect.PostgreSQLDialect
open-in-view: false
generate-ddl: true
Spring Data JPA – Student Entity
A Student object as JPA entity.
package com.knf.dev.demo.entity;
import jakarta.persistence.*;
@Entity
@Table(name = "student")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@Column(name = "first_name")
private String firstName;
@Column(name = "last_name")
private String lastName;
private String email;
private Integer age;
private String gender;
private String qualification;
private String grade;
public Student() {
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getQualification() {
return qualification;
}
public void setQualification(String qualification) {
this.qualification = qualification;
}
public String getGrade() {
return grade;
}
public void setGrade(String grade) {
this.grade = grade;
}
}
The @Entity annotation specifies that the class is an entity and is mapped to a database table. The @Table annotation specifies the name of the database table to be used for mapping. The @Id annotation specifies the primary key of an entity and the @GeneratedValue provides for the specification of generation strategies for the values of primary keys. The @Column annotation is used to specify the mapped column for a persistent property or field. If no Column annotation is specified, the default value will be applied.
Spring Data JPA – Student Repository
package com.knf.dev.demo.repository;
import com.knf.dev.demo.entity.Student;
import org.springframework.data.jpa.repository.JpaRepository;
public interface StudentRepository extends JpaRepository<Student, Long> {
}
JpaRepository is a JPA-specific extension of Repository. It contains an API for basic CRUD operations and also API for pagination and sorting.
Create Custom Error Response
package com.knf.dev.demo.exception;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.time.LocalDateTime;
public class CustomErrorResponse {
@JsonFormat(shape = JsonFormat.Shape.STRING,
pattern = "yyyy-MM-dd hh:mm:ss")
private LocalDateTime timestamp;
private int status;
private String error;
public LocalDateTime getTimestamp() {
return timestamp;
}
public void setTimestamp(LocalDateTime timestamp) {
this.timestamp = timestamp;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public String getError() {
return error;
}
public void setError(String error) {
this.error = error;
}
}
Create ResourceNotFoundException
package com.knf.dev.demo.exception;
public class ResourceNotFoundException extends RuntimeException{
private static final long serialVersionUID = 1L;
public ResourceNotFoundException(String message) {
super(message);
}
}
Create GlobalExceptionHandler
package com.knf.dev.demo.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import java.time.LocalDateTime;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<CustomErrorResponse> globalExceptionHandler
(Exception ex, WebRequest request) {
CustomErrorResponse errors = new CustomErrorResponse();
errors.setTimestamp(LocalDateTime.now());
errors.setError(ex.getMessage());
errors.setStatus(HttpStatus.NOT_FOUND.value());
return new ResponseEntity<>(errors, HttpStatus.NOT_FOUND);
}
}
@ControllerAdvice allows us to handle exceptions across the whole application, not just to an individual controller. The @ExceptionHandler is an annotation used to handle the specific exceptions and sending the custom responses to the client.
Create Student Controller
package com.knf.dev.demo.controller;
import java.util.List;
import com.knf.dev.demo.entity.Student;
import com.knf.dev.demo.exception.ResourceNotFoundException;
import com.knf.dev.demo.repository.StudentRepository;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@CrossOrigin(origins = "*")
@RestController
@RequestMapping("/api/v1")
public class StudentController {
private final StudentRepository studentRepository;
public StudentController(StudentRepository studentRepository) {
this.studentRepository = studentRepository;
}
@GetMapping("/students")
public List<Student> getAllStudents() {
return studentRepository.findAll();
}
@GetMapping("/students/{id}")
public ResponseEntity<Student> getStudentById(@PathVariable(value = "id")
Long id) throws ResourceNotFoundException {
Student student = studentRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException
("Student not found for this id :: " + id));
return ResponseEntity.ok().body(student);
}
@PostMapping("/students")
public Student createStudent(@RequestBody Student student) {
return studentRepository.save(student);
}
@PutMapping("/students/{id}")
public ResponseEntity<Student> updateStudent(@PathVariable(value = "id")
Long id, @RequestBody Student studentDto)
throws ResourceNotFoundException {
Student student = studentRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException
("Student not found for this id :: " + id));
student.setEmail(studentDto.getEmail());
student.setLastName(studentDto.getLastName());
student.setFirstName(studentDto.getFirstName());
student.setGender(studentDto.getGender());
student.setAge(studentDto.getAge());
student.setGrade(studentDto.getGrade());
student.setQualification(studentDto.getQualification());
student.setId(id);
final Student updateStudent = studentRepository.save(student);
return ResponseEntity.ok(updateStudent);
}
@DeleteMapping("/students/{id}")
public ResponseEntity<Boolean> deleteStudent(@PathVariable(value = "id")
Long id) throws ResourceNotFoundException {
Student student = studentRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException
("Student not found for this id :: " + id));
studentRepository.delete(student);
return ResponseEntity.ok(true);
}
}
Using the @CrossOrigin annotation to enable cross-origin calls. The @RestController annotation is mainly utilized for building restful web services utilizing Spring MVC. It is a convenience annotation, this annotation itself annotated with @ResponseBody and @Controller annotation. The class annotated with @RestController annotation returns JSON replication in all the methods. @GetMapping annotation for mapping HTTP GET requests onto specific handler methods. @PostMapping annotation for mapping HTTP POST requests onto specific handler methods. @PutMapping annotation for mapping HTTP PUT requests onto specific handler methods. @DeleteMapping annotation for mapping HTTP DELETE requests onto specific handler methods.
Spring Boot Application
package com.knf.dev.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Run the application and verify REST APIs
Step 1: mvn clean install
Step 2: Run the Spring Boot application - mvn spring-boot:run
Add Student:
Update Student:
List of Student:
Delete Student:
2. Frontend Development
How to set up your environment for Angular development using the Angular CLI tool - click here!
Check version of Angular in cmd using below command,
ng version
You see the following output in your terminal:
Creating a New Angular Project
We’ll use Angular CLI to create our example project (angular-material-ui-crud-app). Go back to your terminal and run the following commands:
ng new angular-material-ui-crud-app
You’ll get prompted for a couple of things — If Would you like to add Angular routing? Say y and Which stylesheet format would you like to use? Pick SCSS. This will set up routing in our project and set SCSS as the stylesheets format for components.
Angular Material Installation
Run the following commands:
ng add @angular/material
Generate student-add-update component
ng g c student-add-update
Generate student service
ng g s service/student
Final Project directory:
package.json
The package. json file contains descriptive and functional metadata about a project, such as a name, version, and dependencies. The file provides the npm package manager with various information to help identify the project and handle dependencies.
{ "name": "angular-material-ui-crud-app", "version": "0.0.0", "scripts": { "ng": "ng", "start": "ng serve", "build": "ng build", "watch": "ng build --watch --configuration development", "test": "ng test" }, "private": true, "dependencies": { "@angular/animations": "^15.1.0", "@angular/cdk": "^15.2.9", "@angular/common": "^15.1.0", "@angular/compiler": "^15.1.0", "@angular/core": "^15.1.0", "@angular/forms": "^15.1.0", "@angular/material": "^15.2.9", "@angular/platform-browser": "^15.1.0", "@angular/platform-browser-dynamic": "^15.1.0", "@angular/router": "^15.1.0", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.12.0" }, "devDependencies": { "@angular-devkit/build-angular": "^15.1.2", "@angular/cli": "~15.1.2", "@angular/compiler-cli": "^15.1.0", "@types/jasmine": "~4.3.0", "jasmine-core": "~4.5.0", "karma": "~6.4.0", "karma-chrome-launcher": "~3.1.0", "karma-coverage": "~2.2.0", "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "~2.0.0", "typescript": "~4.9.4" }}
Student Service
The StudentService will be used to get the data from the backend by calling APIs. Update the student.service.ts file inside src/app/service directory with the following code to it -
import { HttpClient } from '@angular/common/http';import { Injectable } from '@angular/core';import { Observable } from 'rxjs';
@Injectable({ providedIn: 'root'})export class StudentService {
constructor(private _http:HttpClient) { }
addStudent(data: any): Observable<any> { return this._http.post('http://localhost:8080/api/v1/students', data); }
updateStudent(id: number, data: any): Observable<any> { return this._http.put(`http://localhost:8080/api/v1/students/${id}`, data); }
getStudentList(): Observable<any> { return this._http.get('http://localhost:8080/api/v1/students'); }
deleteStudent(id: number): Observable<any> { return this._http.delete(`http://localhost:8080/api/v1/students/${id}`); }
}
HttpClient is a built-in service class available in the @angular/common/http package. It has multiple signature and return types for each request. It uses the RxJS observable-based APIs, which designates it returns the observable and what we need to subscribe it. The @Injectable() decorator defines a class as a service in Angular and allows Angular to inject it into a component as a dependency.
Student Add Update Component
StudentAddUpdateComponent component which will be used to add and update student.
Path - src/app/student-add-update/student-add-update.component.ts
import { Component, Inject, OnInit } from '@angular/core';import { FormBuilder, FormGroup } from '@angular/forms';import { StudentService } from '../service/student.service';import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
@Component({ selector: 'app-student-add-update', templateUrl: './student-add-update.component.html', styleUrls: ['./student-add-update.component.scss']})export class StudentAddUpdateComponent implements OnInit{
studentForm:FormGroup;
qualification: string[] =[ 'Doctorate', 'Masters', 'Bachelors', 'Diploma', 'Secondary/High School', ];
grade: string[] =[ 'First-Class', 'Second-Class', 'Third-Class', 'Failed' ];
constructor( private _fb: FormBuilder, private _studentService: StudentService, private _dialogRef: MatDialogRef<StudentAddUpdateComponent>, @Inject(MAT_DIALOG_DATA) public data: any, ){ this.studentForm = this._fb.group({ firstName: '', lastName: '', email: '', age: '', gender: '', qualification: '', grade: '', }) }
ngOnInit(): void { this.studentForm.patchValue(this.data); }
onFormSubmit() { if (this.studentForm.valid) { if (this.data) { this._studentService .updateStudent(this.data.id, this.studentForm.value) .subscribe({ next: (val: any) => { this._dialogRef.close(true); }, error: (err: any) => { console.error(err); }, }); } else { this._studentService.addStudent(this.studentForm.value).subscribe({ next: (val: any) => { this._dialogRef.close(true); }, error: (err: any) => { console.error(err); }, }); } } }}
Student Add Update Template
Path - src/app/student-add-update/student-add-update.component.html
<div mat-dialog-title> <h1>{{data ? 'Student Data Updation Form': 'Student Data Registration Form'}}</h1></div><form [formGroup]="studentForm" (ngSubmit)="onFormSubmit()"><div mat-dialog-content> <div class="row"> <mat-form-field> <mat-label>First Name</mat-label> <input matInput type="text" placeholder="Ex. Sam" formControlName="firstName"> </mat-form-field> <mat-form-field> <mat-label>Last Name</mat-label> <input matInput type="text" placeholder="Ex. Morrison" formControlName="lastName"> </mat-form-field> </div> <div class="row"> <mat-form-field> <mat-label>Age</mat-label> <input matInput placeholder="Ex. 12" type="number" formControlName="age"> </mat-form-field> <mat-form-field> <mat-label>Email</mat-label> <input matInput type="email" placeholder="Ex. dummy@gmail.com" formControlName="email"> </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-group> </div> <div class="row"> <mat-form-field> <mat-label>Qualification</mat-label> <mat-select formControlName="qualification"> <mat-option *ngFor="let value of qualification" [value]="value">{{value}} </mat-option> </mat-select> </mat-form-field>
<mat-form-field> <mat-label>Grade</mat-label> <mat-select formControlName="grade"> <mat-option *ngFor="let value of grade" [value]="value">{{value}} </mat-option> </mat-select> </mat-form-field> </div>
</div><div mat-dialog-actions class="dialogAction"> <button mat-raised-button [mat-dialog-close]="false">Cancel</button> <button mat-raised-button color="accent" type="submit">{{data ? 'Update': 'Save'}}</button></div></form>
Student Add Update Style
.dialogAction{ padding: 0px 25px 20px; button{ flex: 1; }}.row{ display: flex; gap: 12px;
mat-form-field{ width: 100%; }}
Student AppComponent
The AppComponent component which will be used to display a list of students, create a new student, update student and delete a student.
Path - src/app/app.component.ts
import { Component, OnInit, ViewChild } from '@angular/core';import { MatDialog } from '@angular/material/dialog';import { MatPaginator } from '@angular/material/paginator';import { MatSort } from '@angular/material/sort';import { MatTableDataSource } from '@angular/material/table';import { StudentService } from './service/student.service';import { StudentAddUpdateComponent } from './student-add-update/student-add-update.component';
@Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'],})export class AppComponent implements OnInit { displayedColumns: string[] = [ 'id', 'firstName', 'lastName', 'email', 'age', 'gender', 'qualification', 'grade', 'action', ]; dataSource!: MatTableDataSource<any>;
@ViewChild(MatPaginator) paginator!: MatPaginator; @ViewChild(MatSort) sort!: MatSort;
constructor( private _dialog: MatDialog, private _studentService: StudentService, ) {}
ngOnInit(): void { this.getStudentList(); }
openStudentAddUpdateForm() { const dialogRef = this._dialog.open(StudentAddUpdateComponent); dialogRef.afterClosed().subscribe({ next: (val) => { if (val) { this.getStudentList(); } }, }); }
getStudentList() { this._studentService.getStudentList().subscribe({ next: (res) => { this.dataSource = new MatTableDataSource(res); this.dataSource.sort = this.sort; this.dataSource.paginator = this.paginator; }, error: console.log, }); }
applyFilter(event: Event) { const filterValue = (event.target as HTMLInputElement).value; this.dataSource.filter = filterValue.trim().toLowerCase();
if (this.dataSource.paginator) { this.dataSource.paginator.firstPage(); } }
deleteStudent(id:number){ if (window.confirm('Do you want to go ahead?')) { this._studentService.deleteStudent(id).subscribe({ next: (res) => { this.getStudentList(); }, error: console.log, }); } } openEditForm(data: any) { const dialogRef = this._dialog.open(StudentAddUpdateComponent, { data, });
dialogRef.afterClosed().subscribe({ next: (val) => { if (val) { this.getStudentList(); } }, }); }}
Student App Template
Path - src/app/app.component.html
<mat-toolbar color="accent"> <span>Spring Boot 3 + Angular 15 + Material UI CRUD App</span> <span class="example-spacer"></span> <button mat-raised-button (click)=" openStudentAddUpdateForm()"> ADD STUDENT </button> </mat-toolbar>
<mat-form-field> <mat-label>Filter</mat-label> <input matInput (keyup)="applyFilter($event)" placeholder="Ex. Mia" #input> </mat-form-field> <div class="mat-elevation-z8"> <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="age"> <th mat-header-cell *matHeaderCellDef mat-sort-header> Age </th> <td mat-cell *matCellDef="let row"> {{row.age}} </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="qualification"> <th mat-header-cell *matHeaderCellDef mat-sort-header> Qualification </th> <td mat-cell *matCellDef="let row"> {{row.qualification}} </td> </ng-container>
<ng-container matColumnDef="grade"> <th mat-header-cell *matHeaderCellDef mat-sort-header> Grade </th> <td mat-cell *matCellDef="let row"> {{row.grade}} </td> </ng-container>
<ng-container matColumnDef="action"> <th mat-header-cell *matHeaderCellDef mat-sort-header> Action </th> <td mat-cell *matCellDef="let row"> <button mat-icon-button color="primary" (click)="openEditForm(row)"> <mat-icon>edit</mat-icon> </button> <button mat-icon-button color="warn" (click)="deleteStudent(row.id)"> <mat-icon>delete</mat-icon> </button> </td> </ng-container> <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr> <!-- Row 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>
Student App Style
Path - src/app/app.component.scss
.example-spacer { flex: 1 1 auto; }
App Module
Path: /src/app/app.module.ts
app.module.ts is the main file in the sense that it has all the details of components and services you are importing, components to which the application is bootstrapped and stuff.
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 {MatIconModule} from '@angular/material/icon';import {MatToolbarModule} from '@angular/material/toolbar';import {MatButtonModule} from '@angular/material/button';import {MatDialogModule} from '@angular/material/dialog';import {MatFormFieldModule} from '@angular/material/form-field';import {MatInputModule} from '@angular/material/input';import {MatRadioModule} from '@angular/material/radio';import { StudentAddUpdateComponent } from './student-add-update/student-add-update.component';import {MatSelectModule} from '@angular/material/select';import { ReactiveFormsModule } from '@angular/forms';import {MatTableModule} from '@angular/material/table';import { MatPaginatorModule } from '@angular/material/paginator';import { MatSortModule } from '@angular/material/sort';import { HttpClientModule } from '@angular/common/http';
@NgModule({ declarations: [ AppComponent, StudentAddUpdateComponent ], imports: [ HttpClientModule, BrowserModule, AppRoutingModule, BrowserAnimationsModule, MatToolbarModule, MatIconModule, MatButtonModule, MatDialogModule, MatFormFieldModule, MatInputModule, MatRadioModule, MatSelectModule, ReactiveFormsModule, MatTableModule, MatPaginatorModule, MatSortModule, ], providers: [], bootstrap: [AppComponent]})export class AppModule { }
app-routing.module.ts
Path: /src/app/app-routing.module.ts
import { NgModule } from '@angular/core';import { RouterModule, Routes } from '@angular/router';
const routes: Routes = [];
@NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule]})export class AppRoutingModule { }
index.html
Path: /src/index.html
<!doctype html><html lang="en"><head> <meta charset="utf-8"> <title>AngularMaterialUiCrudApp</title> <base href="/"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" type="image/x-icon" href="favicon.ico"> <link rel="preconnect" href="https://fonts.gstatic.com"> <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"></head><body class="mat-typography"> <app-root></app-root></body></html>
Main/Bootstrap File
Path: /src/main.ts
The main file is the entry point used by angular to launch and bootstrap the application
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';import { AppModule } from './app/app.module';
platformBrowserDynamic().bootstrapModule(AppModule) .catch(err => console.error(err));
Local Setup and Run the application
Step 1: Download or clone the source code from GitHub to a local machine - Click here
Backend
Step 2: mvn clean install
Step 3: Run the Spring Boot application - mvn spring-boot:run
Frontend
Step 4: npm install
Step 5: ng serve
From the browser call the endpoint http://localhost:4200/