Study

Spring Boot Full Stack with Angular Application

Credits / Notes taken from:


Resources:


Table of Contents:


Prerequisites / Needs to be installed:


What is REST (API)?

https://30secondsofinterviews.org/

What is REST API


Back-End

Project Setup

Installing Java

However, the “javac” command won’t work (we can’t compile java to binary classes with Command Prompt using the default Windows’s Java)

(Recommended) You can download the JDK (Java Development Toolkit) separately from here: https://www.oracle.com/java/technologies/downloads/#jdk17-windows

Setup Java for Windows (Command Prompt) - w3schools

  1. Go to “System Properties” (Can be found on Control Panel > System and Security > System > Advanced System Settings)
  2. Click on the “Environment variables” button under the “Advanced” tab
  3. Then, select the “Path” variable in System variables and click on the “Edit” button
  4. Click on the “New” button and add the path where Java is installed, followed by \bin. By default, Java is installed in C:\Program Files\Java\jdk-11.0.1 (If nothing else was specified when you installed it). In that case, You will have to add a new path with: C:\Program Files\Java\jdk-11.0.1\bin Then, click “OK”, and save the settings
  5. Restart PC
  6. Open Command Prompt (cmd.exe) and type java -version to see if Java is running on your machine

Now we can also run “javac” command.


Installing Maven

(Sunday, August 14, 2022)

Note that we need to have Apache Maven installed on our machine.

You can download the “Binary zip archive”. Then follow the install notes from Maven README.txt file:

  Installing Maven
  ----------------

  1) Unpack the archive where you would like to store the binaries, e.g.:

    Unix-based operating systems (Linux, Solaris and Mac OS X)
      tar zxvf apache-maven-3.x.y.tar.gz
    Windows
      unzip apache-maven-3.x.y.zip

  2) A directory called "apache-maven-3.x.y" will be created.

  3) Add the bin directory to your PATH, e.g.:

    Unix-based operating systems (Linux, Solaris and Mac OS X)
      export PATH=/usr/local/apache-maven-3.x.y/bin:$PATH
    Windows
      set PATH="c:\program files\apache-maven-3.x.y\bin";%PATH%

  4) Make sure JAVA_HOME is set to the location of your JDK

  5) Run "mvn --version" to verify that it is correctly installed.

SpringInitializr

SpringInitializr


Initialize project with Spring Initializr

Spring Initializr - Initializr generates spring boot project with just what you need to start quickly!

Project configuration:

Dependencies:

SpringInitializr

We can click “Explore” button to look at our Project’s pom.xml file.

From https://maven.apache.org/guides/introduction/introduction-to-the-pom.html:

A Project Object Model or POM is the fundamental unit of work in Maven. It is an XML file that contains information about the project and configuration details used by Maven to build the project. It contains default values for most projects. Examples for this is the build directory, which is target; the source directory, which is src/main/java; the test source directory, which is src/test/java; and so on. When executing a task or goal, Maven looks for the POM in the current directory. It reads the POM, gets the needed configuration information, then executes the goal.

Some of the configuration that can be specified in the POM are the project dependencies, the plugins or goals that can be executed, the build profiles, and so on. Other information such as the project version, description, developers, mailing lists and such can also be specified.

The contents of pom.xml file that will be generated:

<?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>2.7.2</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>
  <groupId>com.radubulai</groupId>
  <artifactId>employeemanager</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>employeemanager</name>
  <description>Employee Manager App</description>
  <properties>
    <java.version>1.8</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>mysql</groupId>
      <artifactId>mysql-connector-java</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>

Finally, we can click on “GENERATE” button to generate our Spring project! And we can extract the employeemanager.zip archive into a employeemanager folder.

Open project with IntelliJ

We can open our project in IntelliJ IDEA by opening the pom.xml file -> “Open as Project”.

SpringInitializr

And we should see our main java file of our application:

SpringInitializr




(Optional) Useful links/settings on IntelliJ IDEA:

SpringInitializr


Create Employee model class

To create a first model class, we right click on our main project package -> New -> Package -> “model”.

Inside our “model” package, we right click -> New -> New Java Class -> “Employee”.

SpringInitializr

Our employee model will have the following fields:

// Employee.java
package com.radubulai.employeemanager.model;

import java.io.Serializable;

public class Employee implements Serializable {
    private Long id;
    private String name;
    private String email;
    private String jobTitle;
    private String phone;
    private String imageUrl;
    private String employeeCode;
}

We will also have our class been implemented from the Serializable interface (our class “inherits” the Serializable class) in order to be able to “transform” this Java “Employee” Class into different types of strings (because this class will be saved in a database, and it should be able for this class to be converted in something like e.g. JSON). So it’s always a good practice to make model classes being implemented as Serializable.


Now we want this class to be in a database - that’s why we added “Spring Data JPA (Java Persistence API)”:

// Employee.java
package com.radubulai.employeemanager.model;

import javax.persistence.*;
import java.io.Serializable;

@Entity
public class Employee implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(nullable = false, updatable = false)
    private Long id;
    private String name;
    private String email;
    private String jobTitle;
    private String phone;
    private String imageUrl;
    @Column(nullable = false, updatable = false)
    private String employeeCode;
}

SpringInitializr


Now, in this same file Employee.java, we will generate all the constructors and getters and setters. Note that we can use IntelliJ’s IDEA “Generate…” menu for automatic generation of these methods: (instead of writing them manually). Right click on public class Employee implements Serializable:

All the code for src/main/java/com/radubulai/employeemanager/model/Employee.java:

// Employee.java
package com.radubulai.employeemanager.model;

import javax.persistence.*;
import java.io.Serializable;

@Entity
public class Employee implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(nullable = false, updatable = false)
    private Long id;
    private String name;
    private String email;
    private String jobTitle;
    private String phone;
    private String imageUrl;
    @Column(nullable = false, updatable = false)
    private String employeeCode;

    public Employee() {}

    public Employee(Long id, String name, String email, String jobTitle, String phone, String imageUrl, String employeeCode) {
        this.id = id;
        this.name = name;
        this.email = email;
        this.jobTitle = jobTitle;
        this.phone = phone;
        this.imageUrl = imageUrl;
        this.employeeCode = employeeCode;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getJobTitle() {
        return jobTitle;
    }

    public void setJobTitle(String jobTitle) {
        this.jobTitle = jobTitle;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public String getImageUrl() {
        return imageUrl;
    }

    public void setImageUrl(String imageUrl) {
        this.imageUrl = imageUrl;
    }

    public String getEmployeeCode() {
        return employeeCode;
    }

    public void setEmployeeCode(String employeeCode) {
        this.employeeCode = employeeCode;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", email='" + email + '\'' +
                ", jobTitle='" + jobTitle + '\'' +
                ", phone='" + phone + '\'' +
                ", imageUrl='" + imageUrl + '\'' +
                ", employeeCode='" + employeeCode + '\'' +
                '}';
    }
}


Database Configuration

Spring Boot Full Stack with Angular - Amigoscode - 0h20m

After installing MySQL 8.0 (448MB installer), we can open “MySQL 8.0 Command Line Client” (from Windows Start Menu), and type commands like show databases;.


🔵 Note: If we cannot start the MySQL Server (eg. “MySQL Workbench” just crashes when we try to start the server):


To set up our database in our Java Spring Application, we need to go to src/main/resource/application.properties:


application.properties:

# MySQL Configuration
spring.datasource.url=jdbc:mysql://localhost:3306/employeemanager
spring.datasource.username=root
spring.datasource.password=yourpassword
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect


Let’s now create the employeemanager database in MySQL:


We can run our java application by clicking on “Run Application” on our main java function (in EmployeemanagerApplication.java):

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.7.2)

2022-08-14 21:09:58.036  INFO 8364 --- [           main] c.r.e.EmployeemanagerApplication         : Starting EmployeemanagerApplication using Java 17.0.1 on radu-legion with PID 8364 (E:\Proiecte SSD\SpringBoot\employeemanager\target\classes started by radu in E:\Proiecte SSD\SpringBoot\employeemanager)


🟢 Note: we can also run the Java Application from Terminal, in the main project directory:

# E:\Proiecte SSD\SpringBoot\employeemanager>
mvn spring-boot:run

🟢 Also note, that after running the app, the Employee table from the employeemanager database is created automatically in MySQL and can be seen via MySQL Workbench App:


Java CRUD Operations in Database - Service

Spring Boot Full Stack with Angular - Amigoscode - 0h26m: Employee Repository

All these operations will be created under a “Employee Repository” Java package (with the name of repo package) and a service package as controller.


Inheriting JPA Repository

For the repo package:

(Note: To inherit from a class, the extends keyword is used, eg. we could have an Animal/User class, and Dog/Author extends that class - inherits all the properties from Animal/User main class)

// ../repo/EmployeeRepo.java
package com.radubulai.employeemanager.repo;

import com.radubulai.employeemanager.model.Employee;
import org.springframework.data.jpa.repository.JpaRepository;

public interface EmployeeRepo extends JpaRepository<Employee, Long> {

}


Employee Service

For the service package that we are going to use for our “Controller”:

From https://www.digitalocean.com/community/tutorials/spring-service-annotation Spring @Service annotation is used with classes that provide some business functionalities. Spring context will autodetect these classes when annotation-based configuration and classpath scanning is used.

From https://www.baeldung.com/spring-autowire Starting with Spring 2.5, the framework introduced annotations-driven Dependency Injection. The main annotation of this feature is @Autowired. It allows Spring to resolve and inject collaborating beans into our bean.

The Spring framework enables automatic dependency injection. In other words, by declaring all the bean dependencies in a Spring configuration file, Spring container can autowire relationships between collaborating beans. This is called Spring bean autowiring.

// ../service/EmployeeService.java
package com.radubulai.employeemanager.service;

import com.radubulai.employeemanager.repo.EmployeeRepo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class EmployeeService {
    private final EmployeeRepo employeeRepo;

    @Autowired
    public EmployeeService(EmployeeRepo employeeRepo) {
        this.employeeRepo = employeeRepo;
    }
}


And now, we can define all our CRUD operations within this EmployeeService class:

  1. addEmployee
// ../service/EmployeeService.java
public Employee addEmployee(Employee employee) {
    employee.setEmployeeCode(UUID.randomUUID().toString());
    return employeeRepo.save(employee);
}

Spring Boot Full Stack with Angular - Amigoscode - 0h31m: Service class


  1. findAllEmployees - Return (View) the list of all employees
// ../service/EmployeeService.java
public List<Employee> findAllEmployees() {
    return employeeRepo.findAll();
}


  1. updateEmployee
// ../service/EmployeeService.java
public Employee updateEmployee(Employee employee) {
    return employeeRepo.save(employee);
}


  1. deleteEmployee
// ../service/EmployeeService.java
public void deleteEmployee(Long id) {
    employeeRepo.deleteEmployeeById(id);
}
// ../repo/EmployeeRepo.java
public interface EmployeeRepo extends JpaRepository<Employee, Long> {
    void deleteEmployeeById(Long id);
}


  1. findEmployeeById - Find/Return an employee by Id
// ../service/EmployeeService.java
public Employee findEmployeeById(Long id) {
    return employeeRepo.findEmployeeById(id);
}
public interface EmployeeRepo extends JpaRepository<Employee, Long> {
    void deleteEmployeeById(Long id);
    Optional<Employee> findEmployeeById(Long id);
}

// ../service/EmployeeService.java
public Employee findEmployeeById(Long id) {
    return employeeRepo.findEmployeeById(id).orElseThrow(
            () -> new UserNotFoundException("User by id " + id + " was not found")
    );
}

Handling Exceptions:

// ../exception/UserNotFoundException
public class UserNotFoundException extends RuntimeException {
    public UserNotFoundException(String message) {
        super(message);
    }
}



Finally, these were all the methods for the EmployeeService.java:

// Complete code for ../service/EmployeeService.java
package com.radubulai.employeemanager.service;

import com.radubulai.employeemanager.exception.UserNotFoundException;
import com.radubulai.employeemanager.model.Employee;
import com.radubulai.employeemanager.repo.EmployeeRepo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.UUID;

@Service
public class EmployeeService {
    private final EmployeeRepo employeeRepo;

    @Autowired
    public EmployeeService(EmployeeRepo employeeRepo) {
        this.employeeRepo = employeeRepo;
    }

    public Employee addEmployee(Employee employee) {
        employee.setEmployeeCode(UUID.randomUUID().toString());
        return employeeRepo.save(employee);
    }

    public List<Employee> findAllEmployees() {
        return employeeRepo.findAll();
    }

    public Employee updateEmployee(Employee employee) {
        return employeeRepo.save(employee);
    }

    public void deleteEmployee(Long id) {
        employeeRepo.deleteEmployeeById(id);
    }

    public Employee findEmployeeById(Long id) {
        return employeeRepo.findEmployeeById(id).orElseThrow(
                () -> new UserNotFoundException("User by id " + id + " was not found")
        );
    }
}
// Complete code for ../repo/EmployeeRepo.java
package com.radubulai.employeemanager.repo;

import com.radubulai.employeemanager.model.Employee;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface EmployeeRepo extends JpaRepository<Employee, Long> {

    void deleteEmployeeById(Long id);

    Optional<Employee> findEmployeeById(Long id);
}


Exposing the API - Controller

(Monday, August 15, 2022)

Spring Boot Full Stack with Angular - Amigoscode - 0h39m: Exposing the API


Let’s create our controller class:

// EmployeeResource.java
@RestController
@RequestMapping("/employee")
public class EmployeeResource {
    private final EmployeeService employeeService;

    public EmployeeResource(EmployeeService employeeService) {
        this.employeeService = employeeService;
    }
}

From https://www.baeldung.com/spring-controller-vs-restcontroller Spring 4.0 introduced the @RestController annotation in order to simplify the creation of RESTful web services. It’s a convenient annotation that combines @Controller and @ResponseBody, which eliminates the need to annotate every request handling method of the controller class with the @ResponseBody annotation.

We can annotate classic controllers with the @Controller annotation. This is simply a specialization of the @Component class, which allows us to auto-detect implementation classes through the classpath scanning.

We typically use @Controller in combination with a @RequestMapping annotation for request handling methods.


  1. getAllEmployees (GET HTTP Request)

Now, let’s create our first (exposed) method (as a REST API) that returns all of our employees from the database:

// EmployeeResource.java
@RestController
@RequestMapping("/employee")
public class EmployeeResource {
    private final EmployeeService employeeService;

    public EmployeeResource(EmployeeService employeeService) {
        this.employeeService = employeeService;
    }

    @GetMapping("/all")
    public ResponseEntity<List<Employee>> getAllEmployees() {
        List<Employee> employees = employeeService.findAllEmployees();
        return new ResponseEntity<>(employees, HttpStatus.OK);
    }
}


  1. getEmployeeById (GET HTTP Request)

This method will return/find only one user/employee based on provided Id:

// EmployeeResource.java
@GetMapping("/find/{id}")
public ResponseEntity<Employee> getEmployeeById(@PathVariable("id") Long id) {
    Employee employee = employeeService.findEmployeeById(id);
    return new ResponseEntity<>(employee, HttpStatus.OK);
}


  1. addEmployee (POST HTTP Request)

This method will add/create a new employee:

// EmployeeResource.java
@PostMapping("/add")
public ResponseEntity<Employee> addEmployee(@RequestBody Employee employee) {
    Employee newEmployee = employeeService.addEmployee(employee);
    return new ResponseEntity<>(newEmployee, HttpStatus.CREATED);
}


  1. updateEmployee (PUT HTTP Request)

This method will update an existing employee:

// EmployeeResource.java
@PutMapping("/update")
public ResponseEntity<Employee> updateEmployee(@RequestBody Employee employee) {
    Employee updatedEmployee = employeeService.updateEmployee(employee);
    return new ResponseEntity<>(updatedEmployee, HttpStatus.OK);
}


  1. deleteEmployee (DELETE HTTP Request)

This method will delete an employee from database based on provided Id through request URL/Path:

// EmployeeResource.java
@Transactional
@DeleteMapping("/delete/{id}")
public ResponseEntity<?> deleteEmployee(@PathVariable("id") Long id) {
    employeeService.deleteEmployee(id);
    return new ResponseEntity<>(HttpStatus.OK);
}

🔵 Note: The Delete method in Spring will also need @Transactional annotation (see more here https://stackoverflow.com/questions/32269192/spring-no-entitymanager-with-actual-transaction-available-for-current-thread), or else we will receive cannot reliably process 'remove' call with root cause error from Spring.



Finally, now we can run this application and test those endpoints!


Complete code for src/main/java/.../employeemanager/EmployeeResource.java:

// Complete code for EmployeeResource.java
package com.radubulai.employeemanager;

import com.radubulai.employeemanager.model.Employee;
import com.radubulai.employeemanager.service.EmployeeService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/employee")
public class EmployeeResource {
    private final EmployeeService employeeService;

    public EmployeeResource(EmployeeService employeeService) {
        this.employeeService = employeeService;
    }

    @GetMapping("/all")
    public ResponseEntity<List<Employee>> getAllEmployees() {
        List<Employee> employees = employeeService.findAllEmployees();
        return new ResponseEntity<>(employees, HttpStatus.OK);
    }

    @GetMapping("/find/{id}")
    public ResponseEntity<Employee> getEmployeeById(@PathVariable("id") Long id) {
        Employee employee = employeeService.findEmployeeById(id);
        return new ResponseEntity<>(employee, HttpStatus.OK);
    }

    @PostMapping("/add")
    public ResponseEntity<Employee> addEmployee(@RequestBody Employee employee) {
        Employee newEmployee = employeeService.addEmployee(employee);
        return new ResponseEntity<>(newEmployee, HttpStatus.CREATED);
    }

    @PutMapping("/update")
    public ResponseEntity<Employee> updateEmployee(@RequestBody Employee employee) {
        Employee updatedEmployee = employeeService.updateEmployee(employee);
        return new ResponseEntity<>(updatedEmployee, HttpStatus.OK);
    }

    @Transactional
    @DeleteMapping("/delete/{id}")
    public ResponseEntity<?> deleteEmployee(@PathVariable("id") Long id) {
        employeeService.deleteEmployee(id);
        return new ResponseEntity<>(HttpStatus.OK);
    }
}


Testing with Postman

Spring Boot Full Stack with Angular - Amigoscode - 0h50m: Testing

You can download Postman from here: https://www.postman.com/downloads/ (Note as Monday, August 15, 2022, it might need an account in order to use Postman).


Run the application (mvn spring-boot:run in terminal).

🟢 Note that our Java Spring application runs on port 8080 by default (http://localhost:8080/). So we can already make a GET request to http://localhost:8080/employee/all with our browser (Chrome/Edge/Firefox/etc).


In Postman, open a new tab… we should see our interface looking like this:


🔵 To send a GET request to http://localhost:8080/employee/all (getAllEmployees), we just need to add this URL here:

Currently, our request will send us back a response with an empty list (because we don’t have any employees/rows in our MySQL database).


🔵 So, let’s add some users/employees (addEmployee)!

{
    "email": "alex@example.com",
    "imageUrl": "https://placekitten.com/200/300",
    "jobTitle": "Developer",
    "name": "Alex Craig",
    "phone": "4570090332"
}

or

{
    "email": "andrew@example.com",
    "imageUrl": "https://placekitten.com/200/300",
    "jobTitle": "Developer",
    "name": "Andrew Johnson",
    "phone": "4570012342"
}

We will receive back (as a response) our complete Employee object (as JSON), and this employee has now been added to the database as well (we can check with MySQL Workbench)! We can also see the request status code of 201 (created)

We can also see the rows from this Employee table (on employeemanager database) from the “MySQL 8.0 Command Line Client”. We need to run these commands:

show databases;
use employeemanager;
show tables;
select * from employee;


Note that in our terminal (that is running our Spring Java App), we could also see the SQL queries that the application has sent (when receiving requests from Postman) to the MySQL database. That’s because we have set spring.jpa.show-sql=true to true in our application.properties file.

Hibernate: select employee0_.id as id1_0_, employee0_.email as email2_0_, employee0_.employee_code as employee3_0_, employee0_.image_url as image_ur4_0_, employee0_.job_title as job_titl5_0_, employee0_.name as name6_0_, employee0_.phone as phone7_0_ from employee employee0_

Hibernate: insert into employee (email, employee_code, image_url, job_title, name, phone) values (?, ?, ?, ?, ?, ?)


🔵 Find one user by its Id (getEmployeeById) using Postman


🔵 Let’s update an employee (updateEmployee)!

{
    "id": 2,
    "name": "Andrew Johnson",
    "email": "andrew@example.com",
    "jobTitle": "Developer",
    "phone": "4570012342",
    "imageUrl": "https://placekitten.com/200/300",
    "employeeCode": "71703511-6ffe-4b29-8b00-bedd5c7c74b9"
}
{
    "id": 2,
    "name": "Andrew Merlin",
    "email": "andrew@example.com",
    "jobTitle": "Developer",
    "phone": "4570012342",
    "imageUrl": "https://placekitten.com/200/200",
    "employeeCode": "71703511-6ffe-4b29-8b00-bedd5c7c74b9"
}



Testing with HTTPie

Spring Boot Full Stack with Angular - Amigoscode - 0h57m: Testing with HTTPie

We can use HTTPie to do the same tests that we did with Postman.

This tutorial has skipped this part.

Note that making request with HTTPie (CLI) instead of Postman (that has a GUI) is useful for applications that are stored in the cloud, so we can just SSH (or use Putty) into the PC/Server/Machine on cloud and start testing the API right there, on terminal.

We can also use cURL instead of HTTPie.


Front-End

Spring Boot Full Stack with Angular - Amigoscode - 1h03m: Creating Angular App

We will use Angular CLI to create our Front-End Angular Application!

### Basic example

# Install the Angular CLI: To install the Angular CLI globally,
# open a terminal window and run the following command
# (You will run this only once):
npm install -g @angular/cli

# To create a new project (a new workspace for an app)
ng new employeemanager-angular

# Change directory
cd employeemanager-angular

# The Angular CLI includes a server, for you to build and serve your app locally
ng serve --open

Our new app configuration:


Front-End App Architecture (Client):


Entire App Architecture (Front-End + Back-End):

(Saturday, September 03, 2022)


Angular Service (Requests) and Employee Interface Data Type

First we need to create employee.ts interface and employee.service.ts where we can specify the type of data that the request are going to return.


🟠 If we don’t import the HttpClientModule we will receive the following error from Angular in our browser console: ERROR NullInjectorError: R3InjectorError(AppModule)[EmployeeService -> HttpClient -> HttpClient -> HttpClient]: NullInjectorError: No provider for HttpClient!

// app.module.ts
import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { HttpClientModule } from "@angular/common/http";

import { AppComponent } from "./app.component";

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, HttpClientModule],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}


// employee.ts
export interface Employee {
  id: number;
  name: string;
  email: string;
  jobTitle: string;
  phone: string;
  imageUrl: string;
  employeeCode: string;
}


We will generate the “employee” service that will contain all the methods for the HTTP Requests. With Angular’s CLI, in the Angular project path, run:

ng generate service employee
// employee.service.ts
import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { Observable } from "rxjs";
import { Employee } from "./employee";

@Injectable({
  providedIn: "root",
})
export class EmployeeService {
  private apiServerUrl = "";

  constructor(private http: HttpClient) {}

  public getEmployees(): Observable<any> {
    return this.http.get<Employee[]>(`${this.apiServerUrl}/employee/all`);
  }

  public getEmployeeById(employeeId: number): Observable<Employee> {
    return this.http.get<Employee>(
      `${this.apiServerUrl}/employee/delete/${employeeId}`
    );
  }

  public addEmployee(employee: Employee): Observable<Employee> {
    return this.http.post<Employee>(
      `${this.apiServerUrl}/employee/add`,
      employee
    );
  }

  public updateEmployee(employee: Employee): Observable<Employee> {
    return this.http.put<Employee>(
      `${this.apiServerUrl}/employee/update`,
      employee
    );
  }

  public deleteEmployee(employeeId: number): Observable<void> {
    return this.http.delete<void>(
      `${this.apiServerUrl}/employee/delete/${employeeId}`
    );
  }
}


Note that on the Back-End Java Spring Controller (EmployeeResource.java) we had the following routes/methods:

getAllEmployees() -> GET /employee/all

getEmployeeById(@PathVariable("id") Long id) -> GET /employee/find/{id}

addEmployee(@RequestBody Employee employee) -> POST /employee/add

updateEmployee(@RequestBody Employee employee) -> PUT /employee/update

deleteEmployee(@PathVariable("id") Long id) -> DELETE /employee/delete/{id}


Now, here in /src/app/employee.service.ts, we will replace the private apiServerUrl = ''; with an environment variable:

// environment.ts
export const environment = {
  production: false,
  apiBaseUrl: "http://localhost:8080",
};
// employee.service.ts
import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { Observable } from "rxjs";
import { environment } from "src/environments/environment";
import { Employee } from "./employee";

@Injectable({
  providedIn: "root",
})
export class EmployeeService {
  private apiServerUrl = environment.apiBaseUrl;

  constructor(private http: HttpClient) {}

  public getEmployees(): Observable<any> {
    return this.http.get<Employee[]>(`${this.apiServerUrl}/employee/all`);
  }

  public getEmployeeById(employeeId: number): Observable<Employee> {
    return this.http.get<Employee>(
      `${this.apiServerUrl}/employee/delete/${employeeId}`
    );
  }

  public addEmployee(employee: Employee): Observable<Employee> {
    return this.http.post<Employee>(
      `${this.apiServerUrl}/employee/add`,
      employee
    );
  }

  public updateEmployee(employee: Employee): Observable<Employee> {
    return this.http.put<Employee>(
      `${this.apiServerUrl}/employee/update`,
      employee
    );
  }

  public deleteEmployee(employeeId: number): Observable<void> {
    return this.http.delete<void>(
      `${this.apiServerUrl}/employee/delete/${employeeId}`
    );
  }
}


Angular component

Spring Boot Full Stack with Angular - Amigoscode - 1h19m: Angular Component

Since we have the employee service, we can use it now in our component.

Ideally we would have separate component for each part of an application (eg. an EmployeesComponent component for visualising and interacting with employees data, and EmployersComponent component for interacting with employers data, or a TasksComponent component for interacting with tasks data, etc) that we would have created with ng generate component components/employees or ng generate component components/employers, etc.

But, in this tutorial, since we have only the employee table in our database (which suggests a very simple application), we will write all the code in the Angular project’s main app.component


// app.component.ts
import { Component } from "@angular/core";
import { Employee } from "./employee";
import { EmployeeService } from "./employee.service";

@Component({
  selector: "app-root",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"],
})
export class AppComponent {
  title = "employeemanager-angular";
  public employees: Employee[] = [];

  constructor(private employeeService: EmployeeService) {}
}
// app.component.ts
import { HttpErrorResponse } from "@angular/common/http";
import { Component, OnInit } from "@angular/core";
import { Employee } from "./employee";
import { EmployeeService } from "./employee.service";

@Component({
  selector: "app-root",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"],
})
export class AppComponent implements OnInit {
  title = "employeemanager-angular";
  public employees: Employee[] = [];

  constructor(private employeeService: EmployeeService) {}

  ngOnInit(): void {
    this.getEmployees();
  }

  public getEmployees(): void {
    this.employeeService.getEmployees().subscribe(
      (response: Employee[]) => {
        this.employees = response;
      },
      (error: HttpErrorResponse) => {
        console.log(error.message);
      }
    );
  }
}


Now, we can render the employee array of Employee objects (that we get from the back-end REST API server) by removing/deleting all the default Angular’s placeholder code in app.component.html and replacing it with:

<!-- app.component.html -->
<h1>Employee Manager</h1>
<div *ngFor="let employee of employees">
  <div></div>
</div>


Solving “blocked by CORS policy” - CORS Configuration

(Sunday, September 04, 2022)

Spring Boot Full Stack with Angular - Amigoscode - 1h27m: CORS Configuration

Now, if we start the MySQL Server (Start Menu, search and open “Services”, manually find MySQL80 service -> Right click it -> Start), and we are also starting the SpringBoot Back-end Server (mvn spring-boot:run and test on http://localhost:8080/employee/all) and the Front-end Angular Application (ng serve --open on http://localhost:4200/), we will run into the following CORS issue:


About CORS:

From https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS

Cross-Origin Resource Sharing (CORS) is an HTTP-header based mechanism that allows a server to indicate any origins (domain, scheme, or port) other than its own from which a browser should permit loading resources. CORS also relies on a mechanism by which browsers make a “preflight” request to the server hosting the cross-origin resource, in order to check that the server will permit the actual request. In that preflight, the browser sends headers that indicate the HTTP method and headers that will be used in the actual request.

An example of a cross-origin request: the front-end JavaScript code served from https://domain-a.com uses XMLHttpRequest to make a request for https://domain-b.com/data.json.

For security reasons, browsers restrict cross-origin HTTP requests initiated from scripts. For example, XMLHttpRequest and the Fetch API follow the same-origin policy. This means that a web application using those APIs can only request resources from the same origin the application was loaded from unless the response from other origins includes the right CORS headers.


To solve this issue, we need to tell the back-end to allow the front-end app to run on requested url (origin) in order to access the resources.

On the SpringBoot project, in the main application class EmployeemangerApplication we need to add the following CORS Configuration after the main() function:

// EmployeemanagerApplication.java
package com.radubulai.employeemanager;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

import java.util.Arrays;

@SpringBootApplication
public class EmployeemanagerApplication {

    public static void main(String[] args) {
        SpringApplication.run(EmployeemanagerApplication.class, args);
    }

    @Bean
    public CorsFilter corsFilter() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowCredentials(true);
        corsConfiguration.setAllowedOrigins(Arrays.asList("http://localhost:4200"));
        corsConfiguration.setAllowedHeaders(Arrays.asList("Origin", "Access-Control-Allow-Origin", "Content-Type",
                "Accept", "Authorization", "Origin, Accept", "X-Requested-With",
                "Access-Control-Request-Method", "Access-Control-Request-Headers"));
        corsConfiguration.setAllowedHeaders(Arrays.asList("Origin", "Content-Type", "Accept", "Authorization",
                "Access-Control-Allow-Origin", "Access-Control-Allow-Credentials"));
        corsConfiguration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
        UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
        urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
        return new CorsFilter(urlBasedCorsConfigurationSource);
    }
}

Now we can successfully retrieve data / make a GET request from our Angular App:


User Interface - Building the HTML

Basic HTML+CSS Template

(Friday, September 09, 2022)

Spring Boot Full Stack with Angular - Amigoscode - 1h27m: UI Intro

For this project, we can download a HTML+BOOTSTRAP4 template right from this link: https://www.bootdey.com/snippets/view/bs4-contact-cards (instead of designing and creating an interface - UI - from scratch). From this resource, we will only use/only download the HTML and CSS.

We will copy the HTML from Bootdey.com into the app.component.html file:

🔵 Note that we can add for image `` the safe navigation operator (question mark ?), that check if the employee exists (in order to access its attributes) in the first place and has an imageUrl property.

<!-- app.component.html -->
<!-- Navigation bar -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
  <h1 style="font-size:1rem;">
    <a class="navbar-brand" style="color:white;">Employee Manager</a>
  </h1>
  <button
    class="navbar-toggler"
    type="button"
    data-toggle="collapse"
    data-target="#navbarColor02"
    aria-controls="navbarColor02"
    aria-expanded="false"
    aria-label="Toggle navigation"
  >
    <span class="navbar-toggler-icon"></span>
  </button>
  <div class="collapse navbar-collapse" id="navbarColor02">
    <ul class="navbar-nav mr-auto">
      <li class="nav-item active">
        <a class="nav-link"
          >Add Employee <span class="sr-only">(current)</span></a
        >
      </li>
    </ul>
    <form class="form-inline my-2 my-lg-0">
      <input
        type="search"
        id="searchName"
        class="form-control mr-sm-2"
        placeholder="Search employees..."
        required
      />
    </form>
  </div>
</nav>

<!-- Container -->
<div class="container">
  <div class="row">
    <!-- Employee card -->
    <div *ngFor="let employee of employees" class="col-md-6 col-xl-3">
      <div class="card m-b-30">
        <div class="card-body row">
          <div class="col-4">
            <a href="#"
              ><img
                src=""
                alt=""
                title="Picture of "
                class="img-fluid rounded-circle w-60"
            /></a>
          </div>
          <div class="col-8 card-title align-self-center mb-0">
            <div class="card--name"></div>
            <p class="m-0"></p>
          </div>
        </div>
        <ul class="list-group list-group-flush">
          <li class="list-group-item">
            <i class="fa fa-envelope float-right"></i>Email:
            <a href="mailto:"></a>
          </li>
          <li class="list-group-item">
            <i class="fa fa-phone float-right"></i>Phone:
            <a href="tel:"></a>
          </li>
        </ul>
        <div class="card-body">
          <div class="float-right btn-group btn-group-sm">
            <a
              href="#"
              class="btn btn-primary tooltips"
              data-placement="top"
              data-toggle="tooltip"
              data-original-title="Edit"
              ><i class="fa fa-pencil"></i>
            </a>
            <a
              href="#"
              class="btn btn-secondary tooltips"
              data-placement="top"
              data-toggle="tooltip"
              data-original-title="Delete"
              ><i class="fa fa-times"></i
            ></a>
          </div>
          <ul class="social-links list-inline mb-0">
            <li class="list-inline-item">
              <a
                title=""
                data-placement="top"
                data-toggle="tooltip"
                class="tooltips"
                href=""
                data-original-title="LinkedIn"
                ><i class="fa fa-linkedin"></i
              ></a>
            </li>
            <li class="list-inline-item">
              <a
                title=""
                data-placement="top"
                data-toggle="tooltip"
                class="tooltips"
                href=""
                data-original-title="Twitter"
                ><i class="fa fa-twitter"></i
              ></a>
            </li>
            <li class="list-inline-item">
              <a
                title=""
                data-placement="top"
                data-toggle="tooltip"
                class="tooltips"
                href=""
                data-original-title="Skype"
                ><i class="fa fa-skype"></i
              ></a>
            </li>
          </ul>
        </div>
      </div>
    </div>
  </div>
</div>

<!-- Notification for no employees -->
<div *ngIf="employees?.length == 0" class="col-lg-12 col-md-12 col-xl-12">
  <div class="alert alert-info" role="alert">
    <h4 class="alert-heading">NO EMPLOYEES!</h4>
    <p>No Employees were found.</p>
  </div>
</div>


We will copy the CSS into the src/styles.css file and copy the style:

@import "https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css";
@import "htpps://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css";

body {
  background: #fafafa;
  font-size: 16px;
}
.container {
  margin-top: 2rem;
}
.card {
  border: none;
  box-shadow: 1px 2px 5px 1px rgba(0, 0, 0, 0.1);
  margin-bottom: 1rem;
  border-radius: 1rem;
}
.card--name {
  font-size: 1.2rem;
  font-weight: 600;
}
.w-60 {
  width: 4rem;
}

.social-links li a {
  -webkit-border-radius: 50%;
  background-color: rgba(89, 206, 181, 0.85);
  border-radius: 50%;
  color: #fff;
  display: inline-block;
  height: 30px;
  line-height: 30px;
  text-align: center;
  width: 30px;
  font-size: 12px;
}
a {
  color: #707070;
}


Here’s our result:


Adding UI Functionalities

(Saturday, September 10, 2022)

Spring Boot Full Stack with Angular - Amigoscode - 1h40m: UI Modal Logic

When we want to add, edit, or delete an employee, we will have a pop-up modal with the necesary inputs and save/cancel buttons.

The modals will be based on this template: https://getbootstrap.com/docs/4.4/components/modal/. The HTML for these modals will be the following:

<!-- HTML for the ADD, EDIT and DELETE modals -->

<!-- Add Employee Modal -->
<div
  class="modal fade"
  id="addEmployeeModal"
  tabindex="-1"
  role="dialog"
  aria-labelledby="addEmployeeModalLabel"
  aria-hidden="true"
>
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="addEmployeeModalLabel">Add Employee</h5>
        <button
          type="button"
          class="close"
          data-dismiss="modal"
          aria-label="Close"
        >
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body">
        <form #addForm="ngForm" (ngSubmit)="onAddEmloyee(addForm)">
          <div class="form-group">
            <label for="name">Name</label>
            <input
              type="text"
              ngModel
              name="name"
              class="form-control"
              id="name"
              placeholder="Name"
              required
            />
          </div>
          <div class="form-group">
            <label for="email">Email Address</label>
            <input
              type="email"
              ngModel
              name="email"
              class="form-control"
              id="email"
              placeholder="Email"
              required
            />
          </div>
          <div class="form-group">
            <label for="phone">Job title</label>
            <input
              type="text"
              ngModel
              name="jobTitle"
              class="form-control"
              id="jobTile"
              placeholder="Job title"
              required
            />
          </div>
          <div class="form-group">
            <label for="phone">Phone</label>
            <input
              type="text"
              ngModel
              name="phone"
              class="form-control"
              id="phone"
              placeholder="Phone"
              required
            />
          </div>
          <div class="form-group">
            <label for="phone">Image URL</label>
            <input
              type="text"
              ngModel
              name="imageUrl"
              class="form-control"
              id="imageUrl"
              placeholder="Image URL"
              required
            />
          </div>
          <div class="modal-footer">
            <button
              type="button"
              id="add-employee-form"
              class="btn btn-secondary"
              data-dismiss="modal"
            >
              Close
            </button>
            <button
              [disabled]="addForm.invalid"
              type="submit"
              class="btn btn-primary"
            >
              Save changes
            </button>
          </div>
        </form>
      </div>
    </div>
  </div>
</div>
<!-- Edit Modal -->
<div class="modal fade" id="updateEmployeeModal" tabindex="-1" role="dialog" aria-labelledby="employeeEditModalLabel" aria-hidden="true">
  <div class="modal-dialog" role="document">
     <div class="modal-content">
        <div class="modal-header">
           <h5 class="modal-title" id="updateEmployeeModalLabel">Edit Employee </h5>
           <button type="button" class="close" data-dismiss="modal" aria-label="Close">
           <span aria-hidden="true">&times;</span>
           </button>
        </div>
        <div class="modal-body">
           <form #editForm="ngForm">
              <div class="form-group">
                 <label for="name">Name</label>
                 <input type="text" ngModel="" name="name" class="form-control" id="name" aria-describedby="emailHelp" placeholder="Name">
              </div>
              <input type="hidden" ngModel="" name="id" class="form-control" id="id" placeholder="Email">
              <input type="hidden" ngModel="" name="userCode" class="form-control" id="userCode" placeholder="Email">
              <div class="form-group">
                 <label for="email">Email Address</label>
                 <input type="email" ngModel="" name="email" class="form-control" id="email" placeholder="Email">
              </div>
              <div class="form-group">
                 <label for="phone">Job title</label>
                 <input type="text" ngModel="" name="jobTitle" class="form-control" id="jobTitle" placeholder="Job title">
              </div>
              <div class="form-group">
                 <label for="phone">Phone</label>
                 <input type="text" ngModel="" name="phone" class="form-control" id="phone" name="phone" placeholder="Phone">
              </div>
              <div class="form-group">
                 <label for="phone">Image URL</label>
                 <input type="text" ngModel="" name="imageUrl" class="form-control" id="imageUrl" placeholder="Image URL">
              </div>
              <div class="modal-footer">
                 <button type="button" id="" data-dismiss="modal" class="btn btn-secondary">Close</button>
                 <button (click)="onUpdateEmloyee(editForm.value)" data-dismiss="modal" class="btn btn-primary" >Save changes</button>
              </div>
           </form>
        </div>
     </div>
  </div>
</div>
<!-- Delete Modal -->
<div class="modal fade" id="deleteEmployeeModal" tabindex="-1" role="dialog" aria-labelledby="deleteModelLabel" aria-hidden="true">
  <div class="modal-dialog" role="document">
     <div class="modal-content">
        <div class="modal-header">
           <h5 class="modal-title" id="deleteModelLabel">Delete Employee</h5>
           <button type="button" class="close" data-dismiss="modal" aria-label="Close">
           <span aria-hidden="true">&times;</span>
           </button>
        </div>
        <div class="modal-body">
           <p>Are you sure you want to delete employee ?</p>
           <div class="modal-footer">
              <button type="button" class="btn btn-secondary" data-dismiss="modal">No</button>
              <button (click)="onDeleteEmloyee(deleteEmployee?.id)" class="btn btn-danger" data-dismiss="modal">Yes</button>
           </div>
        </div>
     </div>
  </div>
</div>


For the modal logic (opening a modal by pressing a button):

<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" defer></script>
<script
  src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.6/umd/popper.min.js"
  defer
></script>
<script
  src="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/js/bootstrap.min.js"
  defer
></script>

I know, this tutorial is using JQUERY and AJAX for an Angular App 😶… this is not the best practice for a front-end Angular App (or React/Vue/Svelte/etc), in most of the times on a project you will either build the Modal functionalities from scratch via TypeScript and CSS (to show/hide/animate the modal), or you will use an npm package that handles modals…

🔴 Please do not import libraries such as jQuery or Ajax within an Angular/React/Vue/Svelte Front-End App for your personal projects (that are build from scratch) or on your currect company project. This tutorial main focus was to create and consume a REST API made with Java SpringBoot. Such libraries have big dimensions that are needed to be downloaded on client’s PC/browser, along with their hidden implementation complexity could make the app slower.

(From tutorial: “This implementation of creating an artificially invisible button (from onOpenModal()) and virtually pressing it makes our code a bit cleaner.. another method was to implement 3 different functions for add, edit and delete buttons/modals”… Although, personally I would not recommend this implementation at all… because, every time we press an add/edit/delete button to open the desired modal, a button element will be created and added to the DOM in the #main-container div element 😶😶. Luckily, we can just add button.remove(); right after button.click() and everything will be fine ✅)


Debugging Modal Logic

Spring Boot Full Stack with Angular - Amigoscode - 1h55m: Testing Modal Logic

Within our Chrome Browser -> Dev Tools (F12), we can currently check and add breakpoints to our code in order to run the modal functionality line by line.

Our Angular source code can be found on webpack/src/app/app.component.ts path.


Angular Form Logic

Spring Boot Full Stack with Angular - Amigoscode - 1h59m: Testing Modal Logic

First, we need to add the FormsModule in the main app.module.ts file: import { FormsModule } from '@angular/forms';

// app.module.ts
import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { HttpClientModule } from "@angular/common/http";

import { AppComponent } from "./app.component";
import { FormsModule } from "@angular/forms";

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, HttpClientModule, FormsModule],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}


Add Employee Form

🟢 Now, in the app.component.html, in the Add modal, inside <form> we can add an Angular Reference such as <form #addForm="ngForm">. We will also add an action (eg. calling the onAddEmployee method where we will pass in the <form> with all of its data) for the submit: <form #addForm="ngForm" (ngSubmit)="onAddEmployee(addForm)">

See Element references inside a HTML template in Angular: https://ultimatecourses.com/blog/element-refs-in-angular-templates


Note 😶: Ideally, we should have had multiple components, such as a Modal/Form Component, and each Add/Edit/Delete form would have been derived from the main abstract Modal/Form Component (instead of having all the functionalities inside the main app.component files). We’ll just follow along with this tutorial.


<!-- app.component.html -->
<!-- Add Employee Modal -->
<div
  class="modal fade"
  id="addEmployeeModal"
  tabindex="-1"
  role="dialog"
  aria-labelledby="addEmployeeModalLabel"
  aria-hidden="true"
>
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <div class="modal-title" id="addEmployeeModalLabel">Add Employee</div>
        <button
          type="button"
          class="close"
          data-dismiss="modal"
          aria-label="Close"
        >
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body">
        <form #addForm="ngForm" (ngSubmit)="onAddEmployee(addForm)">
          <div class="form-group">
            <label for="name">Name</label>
            <input
              type="text"
              ngModel
              name="name"
              class="form-control"
              id="name"
              placeholder="Name"
              required
            />
          </div>
          <div class="form-group">
            <label for="email">Email Address</label>
            <input
              type="email"
              ngModel
              name="email"
              class="form-control"
              id="email"
              placeholder="Email"
              required
            />
          </div>
          <div class="form-group">
            <label for="phone">Job title</label>
            <input
              type="text"
              ngModel
              name="jobTitle"
              class="form-control"
              id="jobTile"
              placeholder="Job title"
              required
            />
          </div>
          <div class="form-group">
            <label for="phone">Phone</label>
            <input
              type="text"
              ngModel
              name="phone"
              class="form-control"
              id="phone"
              placeholder="Phone"
              required
            />
          </div>
          <div class="form-group">
            <label for="phone">Image URL</label>
            <input
              type="text"
              ngModel
              name="imageUrl"
              class="form-control"
              id="imageUrl"
              placeholder="Image URL"
              required
            />
          </div>
          <div class="modal-footer">
            <button
              type="button"
              id="add-employee-form-close"
              class="btn btn-secondary"
              data-dismiss="modal"
            >
              Close
            </button>
            <button type="submit" class="btn btn-primary">Save changes</button>
          </div>
        </form>
      </div>
    </div>
  </div>
</div>


Demo of Adding a new employee:


Also, after we successfully add an employee, we can clear the form (so the next time we click on “Add” button, the form will be cleared):

// app.component.ts
public onAddEmployee(addForm: NgForm): void {
const methodName = 'onAddEmployee() ';
this.employeeService.addEmployee(addForm.value).subscribe(
  (response: Employee) => {
    console.debug(
      methodName + 'Response Received: ' + JSON.stringify(response)
    );
    this.getEmployees();
    document.getElementById('add-employee-form-close')?.click();
    addForm.reset();
  },
  (error: HttpErrorResponse) => {
    console.error(methodName + error.message);
  }
);
}


Update Employee Form

Spring Boot Full Stack with Angular - Amigoscode - 2h08m: Update Form Functionality

In app.component.ts we will create the onUpdateEmployee method that will take in as parameter an employee; will call the updateEmployee method from employee.service.ts and on a succesful response we will re-render the list of employees.

Note that we have added several debug logs (adding the method name, and on success and on error logs).

// app.component.ts
public onUpdateEmployee(employee: Employee): void {
const methodName = 'onUpdateEmployee() ';
this.employeeService.updateEmployee(employee).subscribe(
  (response: Employee) => {
    console.debug(
      methodName + 'Response Received: ' + JSON.stringify(response)
    );
    this.getEmployees();
    document.getElementById('edit-employee-form-close')?.click();
  },
  (error: HttpErrorResponse) => {
    console.error(methodName + error.message);
  }
);
}

Now, in app.component.ts we will need to call this onUpdateEmployee method… however, we don’t really have access to the current employee object in our HTML for edit modal <form>.

// app.component.ts
import { HttpErrorResponse } from "@angular/common/http";
import { Component, OnInit } from "@angular/core";
import { NgForm } from "@angular/forms";
import { Employee } from "./employee";
import { EmployeeService } from "./employee.service";

@Component({
  selector: "app-root",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"],
})
export class AppComponent implements OnInit {
  title = "employeemanager-angular";
  public employees: Employee[] = [];
  public editEmployee!: Employee;

  constructor(private employeeService: EmployeeService) {}

  ngOnInit(): void {
    this.getEmployees();
  }

  public getEmployees(): void {
    const methodName = "onAddEmployee() ";
    this.employeeService.getEmployees().subscribe(
      (response: Employee[]) => {
        this.employees = response;
        console.debug(
          methodName +
            "Response Received. Showing first two objects: " +
            JSON.stringify(response.slice(0, 2))
        );
      },
      (error: HttpErrorResponse) => {
        console.error(methodName + error.message);
      }
    );
  }

  public onOpenModal(employee: Employee, mode: string): void {
    const container = document.getElementById("main-container");
    const button = document.createElement("button");
    button.type = "button";
    button.style.display = "none";
    button.setAttribute("data-toggle", "modal");

    if (mode == "add") {
      button.setAttribute("data-target", "#addEmployeeModal");
    }
    if (mode == "edit") {
      this.editEmployee = employee;
      button.setAttribute("data-target", "#editEmployeeModal");
    }
    if (mode == "delete") {
      button.setAttribute("data-target", "#deleteEmployeeModal");
    }

    container?.appendChild(button);
    button.click();
    button.remove();
  }

  public onAddEmployee(addForm: NgForm): void {
    const methodName = "onAddEmployee() ";
    this.employeeService.addEmployee(addForm.value).subscribe(
      (response: Employee) => {
        console.debug(
          methodName + "Response Received: " + JSON.stringify(response)
        );
        this.getEmployees();
        document.getElementById("add-employee-form-close")?.click();
      },
      (error: HttpErrorResponse) => {
        console.error(methodName + error.message);
      }
    );
  }

  public onUpdateEmployee(employee: Employee): void {
    const methodName = "onUpdateEmployee() ";
    this.employeeService.updateEmployee(employee).subscribe(
      (response: Employee) => {
        console.debug(
          methodName + "Response Received: " + JSON.stringify(response)
        );
        this.getEmployees();
        document.getElementById("edit-employee-form-close")?.click();
      },
      (error: HttpErrorResponse) => {
        console.error(methodName + error.message);
      }
    );
  }
}
<!-- app.component.html -->
<!-- Edit Modal -->
<div
  class="modal fade"
  id="editEmployeeModal"
  tabindex="-1"
  role="dialog"
  aria-labelledby="employeeEditModalLabel"
  aria-hidden="true"
>
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <div class="modal-title" id="updateEmployeeModalLabel">
          Edit Employee
        </div>
        <button
          type="button"
          class="close"
          data-dismiss="modal"
          aria-label="Close"
        >
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body">
        <form #editForm="ngForm">
          <div class="form-group">
            <label for="name">Name *</label>
            <input
              type="text"
              ngModel=""
              name="name"
              class="form-control"
              id="name"
              aria-describedby="emailHelp"
              required
            />
          </div>
          <input
            type="hidden"
            ngModel=""
            name="id"
            class="form-control"
            id="id"
          />
          <input
            type="hidden"
            ngModel=""
            name="userCode"
            class="form-control"
            id="userCode"
          />
          <div class="form-group">
            <label for="email">Email Address *</label>
            <input
              type="email"
              ngModel=""
              name="email"
              class="form-control"
              id="email"
              placeholder="name@example.com"
              required
            />
          </div>
          <div class="form-group">
            <label for="phone">Job title *</label>
            <input
              type="text"
              ngModel=""
              name="jobTitle"
              class="form-control"
              id="jobTitle"
              placeholder="Developer, Manager, Support, etc"
              required
            />
          </div>
          <div class="form-group">
            <label for="phone">Phone *</label>
            <input
              type="text"
              ngModel=""
              name="phone"
              class="form-control"
              id="phone"
              placeholder="Phone"
              required
            />
          </div>
          <div class="form-group">
            <label for="phone">Image URL *</label>
            <input
              type="text"
              ngModel=""
              name="imageUrl"
              class="form-control"
              id="imageUrl"
              placeholder="Image URL"
              required
            />
          </div>
          <div class="form-group">
            <label for="phone">Website URL</label>
            <input
              type="text"
              ngModel
              name="websiteUrl"
              class="form-control"
              id="websiteUrl"
              placeholder="https://www.employees-website.com"
            />
          </div>
          <div class="form-group">
            <label for="phone">LinkedIn URL</label>
            <input
              type="text"
              ngModel
              name="linkedinUrl"
              class="form-control"
              id="linkedinUrl"
              placeholder="https://www.linkedin.com/in/employee-name/"
            />
          </div>
          <div class="modal-footer">
            <button
              type="button"
              id="edit-employee-form-close"
              data-dismiss="modal"
              class="btn btn-secondary"
            >
              Close
            </button>
            <button
              (click)="onUpdateEmployee(editForm.value)"
              [disabled]="editForm.invalid"
              data-dismiss="modal"
              class="btn btn-primary"
            >
              Save changes
            </button>
          </div>
        </form>
      </div>
    </div>
  </div>
</div>


Update employee code logic:



Delete Employee

(Saturday, September 24, 2022)

Spring Boot Full Stack with Angular - Amigoscode - 2h08m: Delete Functionality

// app.component.ts
public onDeleteEmployee(employeeId: number): void {
  const methodName = 'onDeleteEmployee() ';
  this.employeeService.deleteEmployee(employeeId).subscribe(
    (response: void) => {
      console.debug(
        methodName + 'Response Received: ' + JSON.stringify(response)
      );
      this.getEmployees();
      document.getElementById('delete-employee-form-close')?.click();
    },
    (error: HttpErrorResponse) => {
      console.error(methodName + error.message);
    }
  );
}
// app.component.ts
export class AppComponent implements OnInit {
  title = "employeemanager-angular";
  public employees: Employee[] = [];
  public editEmployee!: Employee | undefined;
  public deleteEmployee!: Employee | undefined;

  /* ... */

  public onOpenModal(employee: Employee, mode: string): void {
    const container = document.getElementById("main-container");
    const button = document.createElement("button");
    button.type = "button";
    button.style.display = "none";
    button.setAttribute("data-toggle", "modal");

    if (mode == "add") {
      button.setAttribute("data-target", "#addEmployeeModal");
    }
    if (mode == "edit") {
      this.editEmployee = employee;
      button.setAttribute("data-target", "#editEmployeeModal");
    }
    if (mode == "delete") {
      this.deleteEmployee = employee;
      button.setAttribute("data-target", "#deleteEmployeeModal");
    }

    container?.appendChild(button);
    button.click();
    button.remove();
  }
}
<!-- app.component.html -->
<!-- Delete Modal -->
<div
  class="modal fade"
  id="deleteEmployeeModal"
  tabindex="-1"
  role="dialog"
  aria-labelledby="deleteModelLabel"
  aria-hidden="true"
>
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <div class="modal-title" id="deleteModelLabel">Delete Employee</div>
        <button
          type="button"
          class="close"
          data-dismiss="modal"
          aria-label="Close"
        >
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body">
        <p>
          Are you sure you want to delete employee ?
        </p>
        <div class="modal-footer">
          <button
            type="button"
            id="delete-employee-form-close"
            class="btn btn-secondary"
            data-dismiss="modal"
          >
            No
          </button>
          <button
            (click)="onDeleteEmployee(deleteEmployee!.id)"
            class="btn btn-danger"
            data-dismiss="modal"
          >
            Yes
          </button>
        </div>
      </div>
    </div>
  </div>
</div>



Angular Search Functionality

Spring Boot Full Stack with Angular - Amigoscode - 2h28m: Search Functionality

For the search functionality, the implementation will be based on indexOf() method.

The indexOf() method returns the first index at which a given element can be found in the array, or -1 if it is not present.

// app.component.ts
  public searchEmployees(keyword: string): void {
    const resultEmployees: Employee[] = [];
    const searchedText = keyword.toLowerCase();
    for (const employee of this.employees) {
      if (
        employee.name.toLowerCase().indexOf(searchedText) !== -1 ||
        employee.email.toLowerCase().indexOf(searchedText) !== -1 ||
        employee.phone.toLowerCase().indexOf(searchedText) !== -1 ||
        employee.jobTitle.toLowerCase().indexOf(searchedText) !== -1
      ) {
        resultEmployees.push(employee);
      }
    }
    this.employees = resultEmployees;

    // if input field for search is empty
    if (!searchedText) {
      this.getEmployees();
    }
  }
<form class="form-inline my-2 my-lg-0">
  <input
    (ngModelChange)="searchEmployees(keyword.value)"
    #keyword="ngModel"
    ngModel
    name="keyword"
    type="search"
    id="searchName"
    class="form-control mr-sm-2"
    title="Search employees by name, email, phone, or job title"
    placeholder="Search employees..."
  />
</form>



Refactoring our app after tutorial

The complete project (MySQL + SpringBoot BackEnd + Angular FrontEnd) can be started in Windows with these steps:


Changing SpringBoot API URLs

First thing that we’ll refactor, are the URLs (routes) that we created for our API in order to be consumed by a front-end application:

We will respect the REST API URLs structure ( from https://30secondsofinterviews.org/ and https://apiguide.readthedocs.io/en/latest/build_and_publish/use_HTTP_methods.html ):

What is REST API

What is REST API


👉 So, instead of having the following URLs (from tutorial):


👍 We will change with the following URLs:



For this, we will change the code within SpringBoot BackEnd EmployeeResource.java file, and the code within Angular FE employee.service.ts file:

// EmployeeResource.java
package com.radubulai.employeemanager;

import com.radubulai.employeemanager.model.Employee;
import com.radubulai.employeemanager.service.EmployeeService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/employees")
public class EmployeeResource {
    private final EmployeeService employeeService;

    public EmployeeResource(EmployeeService employeeService) {
        this.employeeService = employeeService;
    }

    @GetMapping("")
    public ResponseEntity<List<Employee>> getAllEmployees() {
        List<Employee> employees = employeeService.findAllEmployees();
        return new ResponseEntity<>(employees, HttpStatus.OK);
    }

    @GetMapping("/{id}")
    public ResponseEntity<Employee> getEmployeeById(@PathVariable("id") Long id) {
        Employee employee = employeeService.findEmployeeById(id);
        return new ResponseEntity<>(employee, HttpStatus.OK);
    }

    @PostMapping("/new")
    public ResponseEntity<Employee> addEmployee(@RequestBody Employee employee) {
        Employee newEmployee = employeeService.addEmployee(employee);
        return new ResponseEntity<>(newEmployee, HttpStatus.CREATED);
    }

    @PutMapping("/update")
    public ResponseEntity<Employee> updateEmployee(@RequestBody Employee employee) {
        Employee updatedEmployee = employeeService.updateEmployee(employee);
        return new ResponseEntity<>(updatedEmployee, HttpStatus.OK);
    }

    @Transactional
    @DeleteMapping("/{id}")
    public ResponseEntity<?> deleteEmployee(@PathVariable("id") Long id) {
        employeeService.deleteEmployee(id);
        return new ResponseEntity<>(HttpStatus.OK);
    }
}
// employee.service.ts
import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { Observable } from "rxjs";
import { environment } from "src/environments/environment";
import { Employee } from "./employee";

@Injectable({
  providedIn: "root",
})
export class EmployeeService {
  private apiServerUrl = environment.apiBaseUrl + "/api/employees";

  constructor(private http: HttpClient) {}

  public getEmployees(): Observable<any> {
    const methodName = "getEmployees() ";
    console.debug(methodName + "Request Sent");
    return this.http.get<Employee[]>(`${this.apiServerUrl}`);
  }

  public getEmployeeById(employeeId: number): Observable<Employee> {
    const methodName = "getEmployeeById() ";
    console.debug(methodName + "Request Sent: " + employeeId);
    return this.http.get<Employee>(`${this.apiServerUrl}/${employeeId}`);
  }

  public addEmployee(employee: Employee): Observable<Employee> {
    const methodName = "addEmployee() ";
    console.debug(methodName + "Request Sent: " + JSON.stringify(employee));
    return this.http.post<Employee>(`${this.apiServerUrl}/new`, employee);
  }

  public updateEmployee(employee: Employee): Observable<Employee> {
    const methodName = "updateEmployee() ";
    console.debug(methodName + "Request Sent: " + JSON.stringify(employee));
    return this.http.put<Employee>(`${this.apiServerUrl}/update`, employee);
  }

  public deleteEmployee(employeeId: number): Observable<void> {
    const methodName = "deleteEmployee() ";
    console.debug(methodName + "Request Sent: " + employeeId);
    return this.http.delete<void>(`${this.apiServerUrl}/${employeeId}`);
  }
}



Refactoring Angular App

(Wednesday, September 28, 2022)

One of the first simplest thing we can do, instead of having the employee.service.ts (and employee.service.spec.ts) files directly in the main app folder, we can have them in app/services subfolder. For this, we only need to create the seervices subfolder and move these files directly (VS Code will handle the imports in all the other files automatically, eg. for app.component.ts).


Header Navbar Component

Instead of having the entire Navigation Bar (Header) inside app.component.html, we will have it separated as an Angular Component. To create a new Component in Angular, we need to open our terminal in the project’s folder and type:

ng generate component components/header

Also, in order to use methods like onOpenModal and searchEmployees inside this new header component, we can (for now) just import them from import { AppComponent } from 'src/app/app.component';.

// app/components/header/header.component.ts
import { Component, OnInit } from "@angular/core";
import { AppComponent } from "src/app/app.component";
import { Employee } from "src/app/employee";

@Component({
  selector: "app-header",
  templateUrl: "./header.component.html",
  styleUrls: ["./header.component.css"],
})
export class HeaderComponent implements OnInit {
  constructor(private appComponent: AppComponent) {}

  ngOnInit(): void {}

  onOpenModal(employee: Employee, mode: string): void {
    this.appComponent.onOpenModal(employee, mode);
  }

  searchEmployees(keyword: string): void {
    this.appComponent.searchEmployees(keyword);
  }
}



Employee Card Component

ng generate component components/employee-card
<!-- employee-card.component.html -->
<div class="card m-b-30">
  <div class="card-body row">
    <div class="col-4">
      <img
        src=""
        alt=""
        title="Picture of "
        class="card--img img-fluid rounded-circle w-60"
      />
    </div>
    <div class="col-8 card-title align-self-center mb-0">
      <div class="card--name"></div>
      <p class="m-0"></p>
    </div>
  </div>
  <ul class="list-group list-group-flush">
    <li class="list-group-item">
      <i class="fa fa-envelope float-right"></i>Email:
      <a href="mailto:"></a>
    </li>
    <li class="list-group-item">
      <i class="fa fa-phone float-right"></i>Phone:
      <a href="tel:"></a>
    </li>
  </ul>
  <div class="card-body">
    <div class="float-right btn-group btn-group-sm">
      <a
        (click)="onOpenModal(employee, 'edit')"
        href="#"
        class="btn btn-primary tooltips"
        data-placement="top"
        data-toggle="tooltip"
        data-original-title="Edit"
        title="Edit employee"
        ><i class="fa fa-pencil"></i>
      </a>
      <a
        (click)="onOpenModal(employee, 'delete')"
        href="#"
        class="btn btn-secondary tooltips"
        data-placement="top"
        data-toggle="tooltip"
        data-original-title="Delete"
        title="Delete employee"
        ><i class="fa fa-times"></i
      ></a>
    </div>
    <ul class="social-links list-inline mb-0">
      <li *ngIf="employee?.websiteUrl" class="list-inline-item">
        <a
          data-placement="top"
          data-toggle="tooltip"
          class="tooltips"
          href=""
          target="_blank"
          data-original-title="Website"
          title=""
          ><i class="fa fa-globe"></i
        ></a>
      </li>
      <li *ngIf="employee?.linkedinUrl" class="list-inline-item">
        <a
          data-placement="top"
          data-toggle="tooltip"
          class="tooltips"
          href=""
          target="_blank"
          data-original-title="LinkedIn"
          title=""
          ><i class="fa fa-linkedin"></i
        ></a>
      </li>
    </ul>
  </div>
</div>
<!-- Navigation bar -->
<app-header></app-header>

<!-- Container -->
<div class="container" id="main-container">
  <div class="row">
    <!-- Employee card -->
    <app-employee-card
      class="col-md-6 col-xl-3"
      *ngFor="let employee of employees"
      [employee]="employee"
    >
    </app-employee-card>
  </div>
</div>

<!-- Add Employee Modal -->
<div
  class="modal fade"
  id="addEmployeeModal"
  tabindex="-1"
  role="dialog"
  aria-labelledby="addEmployeeModalLabel"
  aria-hidden="true"
>
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <div class="modal-title" id="addEmployeeModalLabel">
          Add new employee
        </div>
        <button
          type="button"
          class="close"
          data-dismiss="modal"
          aria-label="Close"
        >
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body">
        <form #addForm="ngForm" (ngSubmit)="onAddEmployee(addForm)">
          <div class="form-group">
            <label for="name">Name *</label>
            <input
              type="text"
              ngModel
              name="name"
              class="form-control"
              id="name"
              autocomplete="off"
              required
            />
          </div>
          <div class="form-group">
            <label for="email">Email Address *</label>
            <input
              type="email"
              ngModel
              name="email"
              class="form-control"
              id="email"
              placeholder="name@example.com"
              autocomplete="off"
              required
            />
          </div>
          <div class="form-group">
            <label for="phone">Job title *</label>
            <input
              type="text"
              ngModel
              name="jobTitle"
              class="form-control"
              id="jobTile"
              placeholder="Developer, Manager, Support, etc"
              list="jobTitle"
              required
            />
            <datalist id="jobTitle">
              <option value="Project Manager"></option>
              <option value="Developer"></option>
              <option value="Designer"></option>
              <option value="Support"></option>
              <option value="System Admin"></option>
              <option value="SEO Specialist"></option>
              <option value="Recruiter"></option>
              <option value="HR Manager"></option>
            </datalist>
          </div>
          <div class="form-group">
            <label for="phone">Phone *</label>
            <input
              type="text"
              ngModel
              name="phone"
              class="form-control"
              id="phone"
              placeholder="Phone"
              required
            />
          </div>
          <div class="form-group">
            <label for="phone">Image URL *</label>
            <input
              type="text"
              ngModel
              name="imageUrl"
              class="form-control"
              id="imageUrl"
              placeholder="Image URL"
              required
            />
          </div>
          <div class="form-group">
            <label for="phone">Website URL</label>
            <input
              type="text"
              ngModel
              name="websiteUrl"
              class="form-control"
              id="websiteUrl"
              placeholder="https://www.employees-website.com"
            />
          </div>
          <div class="form-group">
            <label for="phone">LinkedIn URL</label>
            <input
              type="text"
              ngModel
              name="linkedinUrl"
              class="form-control"
              id="linkedinUrl"
              placeholder="https://www.linkedin.com/in/employee-name/"
            />
          </div>
          <div class="modal-footer">
            <button
              type="button"
              id="add-employee-form-close"
              class="btn btn-secondary"
              data-dismiss="modal"
            >
              Close
            </button>
            <button
              [disabled]="addForm.invalid"
              type="submit"
              class="btn btn-primary"
            >
              Add employee
            </button>
          </div>
        </form>
      </div>
    </div>
  </div>
</div>

<!-- Edit Modal -->
<div
  class="modal fade"
  id="editEmployeeModal"
  tabindex="-1"
  role="dialog"
  aria-labelledby="employeeEditModalLabel"
  aria-hidden="true"
>
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <div class="modal-title" id="updateEmployeeModalLabel">
          Edit employee 
        </div>
        <button
          type="button"
          class="close"
          data-dismiss="modal"
          aria-label="Close"
        >
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body">
        <form #editForm="ngForm">
          <div class="form-group">
            <label for="name">Name *</label>
            <input
              type="text"
              ngModel=""
              name="name"
              class="form-control"
              id="name"
              aria-describedby="emailHelp"
              autocomplete="off"
              required
            />
          </div>
          <input
            type="hidden"
            ngModel=""
            name="id"
            class="form-control"
            id="id"
          />
          <input
            type="hidden"
            ngModel=""
            name="userCode"
            class="form-control"
            id="userCode"
          />
          <div class="form-group">
            <label for="email">Email Address *</label>
            <input
              type="email"
              ngModel=""
              name="email"
              class="form-control"
              id="email"
              placeholder="name@example.com"
              autocomplete="off"
              required
            />
          </div>
          <div class="form-group">
            <label for="phone">Job title *</label>
            <input
              type="text"
              ngModel=""
              name="jobTitle"
              class="form-control"
              id="jobTitle"
              placeholder="Developer, Manager, Support, etc"
              list="jobTitle"
              required
            />
            <datalist id="jobTitle">
              <option value="Project Manager"></option>
              <option value="Developer"></option>
              <option value="Designer"></option>
              <option value="Support"></option>
              <option value="System Admin"></option>
              <option value="SEO Specialist"></option>
              <option value="Recruiter"></option>
              <option value="HR Manager"></option>
            </datalist>
          </div>
          <div class="form-group">
            <label for="phone">Phone *</label>
            <input
              type="text"
              ngModel=""
              name="phone"
              class="form-control"
              id="phone"
              placeholder="Phone"
              required
            />
          </div>
          <div class="form-group">
            <label for="phone">Image URL *</label>
            <input
              type="text"
              ngModel=""
              name="imageUrl"
              class="form-control"
              id="imageUrl"
              placeholder="Image URL"
              required
            />
          </div>
          <div class="form-group">
            <label for="phone">Website URL</label>
            <input
              type="text"
              ngModel=""
              name="websiteUrl"
              class="form-control"
              id="websiteUrl"
              placeholder="https://www.employees-website.com"
            />
          </div>
          <div class="form-group">
            <label for="phone">LinkedIn URL</label>
            <input
              type="text"
              ngModel=""
              name="linkedinUrl"
              class="form-control"
              id="linkedinUrl"
              placeholder="https://www.linkedin.com/in/employee-name/"
            />
          </div>
          <div class="modal-footer">
            <button
              type="button"
              id="edit-employee-form-close"
              data-dismiss="modal"
              class="btn btn-secondary"
            >
              Close
            </button>
            <button
              (click)="onUpdateEmployee(editForm.value)"
              [disabled]="editForm.invalid"
              data-dismiss="modal"
              class="btn btn-primary"
            >
              Save changes
            </button>
          </div>
        </form>
      </div>
    </div>
  </div>
</div>

<!-- Delete Modal -->
<div
  class="modal fade"
  id="deleteEmployeeModal"
  tabindex="-1"
  role="dialog"
  aria-labelledby="deleteModelLabel"
  aria-hidden="true"
>
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <div class="modal-title" id="deleteModelLabel">
          Delete employee 
        </div>
        <button
          type="button"
          class="close"
          data-dismiss="modal"
          aria-label="Close"
        >
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body">
        <p>
          Are you sure you want to delete employee ?
        </p>
        <div class="modal-footer">
          <button
            type="button"
            id="delete-employee-form-close"
            class="btn btn-secondary"
            data-dismiss="modal"
          >
            No
          </button>
          <button
            (click)="onDeleteEmployee(deleteEmployee!.id)"
            class="btn btn-danger"
            data-dismiss="modal"
          >
            Yes
          </button>
        </div>
      </div>
    </div>
  </div>
</div>

<!-- Notification for no employees -->
<div *ngIf="employees?.length === 0" class="col-lg-12 col-md-12 col-xl-12">
  <div class="alert alert-info" role="alert">
    <div class="alert-heading alert-title">NO EMPLOYEES!</div>
    <p>No Employees were found.</p>
  </div>
</div>
// employee-card.component.ts
import { Component, OnInit, Input } from "@angular/core";
import { Employee } from "src/app/employee";
import { AppComponent } from "src/app/app.component";

@Component({
  selector: "app-employee-card",
  templateUrl: "./employee-card.component.html",
  styleUrls: ["./employee-card.component.css"],
})
export class EmployeeCardComponent implements OnInit {
  @Input() employee!: Employee;

  constructor(private appComponent: AppComponent) {}

  ngOnInit(): void {}

  onOpenModal(employee: Employee, mode: string): void {
    this.appComponent.onOpenModal(employee, mode);
  }
}



Add Employee Modal Component

ng generate component components/modals/add-employee-modal



Edit Employee Modal Component

ng generate component components/modals/edit-employee-modal



Delete Employee Modal Component

ng generate component components/modals/delete-employee-modal