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 17Spring 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

Let’s configure Spring Boot to use PostgreSQL as our data source. We are simply adding PostgreSQL database URL, username, and password in the src/main/resources/application.yaml
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

Path - src/app/student-add-update/student-add-update.component.scss
.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/

Download the complete source code - click here!

Popular posts from this blog

Learn Java 8 streams with an example - print odd/even numbers from Array and List

Java Stream API - How to convert List of objects to another List of objects using Java streams?

Registration and Login with Spring Boot + Spring Security + Thymeleaf

Java, Spring Boot Mini Project - Library Management System - Download

ReactJS, Spring Boot JWT Authentication Example

Spring Boot + Mockito simple application with 100% code coverage

Top 5 Java ORM tools - 2024

Java - Blowfish Encryption and decryption Example

Spring boot video streaming example-HTML5

Google Cloud Storage + Spring Boot - File Upload, Download, and Delete