In this section we will be implementing the frontend of the teacher
class and connecting it with the backend features we created.
Below sections will guide you sequentially on how to implement the frontend of the capstone project.
A component shouldn't fetch data from the server directly. It should only know how to present the fetched data. To decouple fetching the data and presenting the data, Angular
uses Services
. Services act as an intermediate layer between the frontend and the backend.
In the Implementing the Backend section, we created APIs for the CRUD
operations of the Teacher class. Now, let's use the service file given in the Capstone Project Template
to understand how the necessary services are implemented.
If you want to create a
Angular Service
from the scratch, we can use theAngular CLI
. Type the following command in the terminal to create theService
.ng generate service app-service
In your src/app
directory, look out for a file named app-service.service.ts
and open it. Let's try to understand the given code.
The @Injectable
decorator is responsible for marking the AppServiceService
class as a class that participates in the dependency injection system. The metadata providedIn: 'root'
registers a provider with the root injector for the service. That means the service will be provided at root level and Angular
creates a single instance of the object which shared with any class that asks for it.
The readonly variable called ROOT_URL
is used to specify the root/base URL of the backend.
In the constructor a private HttpClient
instance called http
added and the ROOT_URL
is set to the base URL of the backend. In our case, http://localhost:8080
.
constructor(private http: HttpClient) {
this.ROOT_URL = 'http://localhost:8080'
}
The next task is to implement the functions to call the APIs of the backend. Let's start by understanding the function to retrieve teachers.
getTeacherData() {
return this.http.get('/api/listTeachers')
}
The
get
method of theHttpClient
module constructs an observable that, when subscribed, causes the configuredGET
request to execute on the server. For more information on the method, read the following document.
getOneTeacherData(payload: Object) {
return this.http.post('/api/getTeacherInfo', payload)
}
The
post
method of theHttpClient
module is used here as we use aPOST
request to get information of a particular teacher. The necessary data for the request is passed as anjavascript object
to this method.
addTeacher(payload: Object) {
return this.http.post('/api/addTeacher', payload)
}
deleteTeacher(payload: Object) {
return this.http.post('/api/deleteTeacher', payload)
}
editTeacher(payload: Object){
return this.http.post('/api/editTeacher', payload)
}
Routing is the mechanism that is used to tell the browser what to load when a specific URL is called. This is essential in any website that has multiple UIs (User Interfaces).
Angular
provides an in-built routing module called the RouterModule
, which is used to handle the routing of the frontend.
To create a router named
app-routing
from the scratch via theAngular CLI
, type the following command.ng generate module app-routing --flat --module=appHere, the additional parameters,
--flat
- Puts the file insrc/app
instead of its own folder.--module=app
- Tells the CLI to register it in theimports
array of theAppModule
.For, further details on routing in
Angular
, visit the following link.
The app-routing
router of the Capstone Project is in a TypeScript file named app-routing.module.ts
in your src/app
directory. It will contain the following routing template.
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { AddNewTeacherComponent } from './components/add-new-teacher/add-new-teacher.component';
import { EditTeacherComponent } from './components/edit-teacher/edit-teacher.component';
import { TeacherTableComponent } from './components/teacher-table/teacher-table.component';
const routes: Routes = [
{ path: '', component: TeacherTableComponent },
{ path: 'addTeacher', component: AddNewTeacherComponent },
{ path: 'editTeacher', component: EditTeacherComponent }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
First let's understand the imports.
The Routes
and RouterModule
modules are imported from the @angular/router
module and is used to implement the routing functionality.
The next component imports are used to specify which component to load in a particular route.
The next part is the routes
array. This array is used to specify which component should be loaded at a particular route. The path
parameter is used to specify the route and the component
parameter is used to specify the component to be loaded at a particular route.
Ex: Load the
AddNewTeacherComponent
component when the URL<base_url>/addTeacher
is specified.
Important: Component specified in the route
''
will be loaded at thebase URL
.
For more details on these modules, read the following API docs.
Next is the @NgModule
decorator. It is used to mark the class as a NgModule, and it supplies configuration metadata for the class.
Read more about
@NgModule
using the following link
Next, we provide the routes to the AppRoutingModule's import
array using the RouterModule's forRoot()
method. The forRoot(routes)
method is used to tell the component the routes are configured ar the root level of the application.
The exports
array is used to export the RouterModule
so it will be available throughout the application.
After creating the Router
, replace all the code in app.component.html
which is located in the src/app
directory with the following.
<router-outlet></router-outlet>
The selector for the Router
is router-outlet
. Now in the app.component.html
template, the Router
will be loaded when the app
is served.
In this section we will try to understand the functionality of navigation panel for our frontend. It will help a user to navigate between Teachers
, and Students
.
Now, navigate to the navbar.component.ts
file in the components/navbar
directory.
The Router
component we exported in the Routing section is now imported.
import { Router } from '@angular/router';
We will be using this navbar
component to display the Title
of the page as well. Due to that we should make the component able to receive the title
as an input
from its parent. Nothing will be sent from the component to its parent. This is called one-way data binding
and Angular
uses a decorator called @Input
for this purpose.
To access this feature, the @Input
decorator is imported from the @angular/core
.
import { Component, OnInit, Input } from '@angular/core';
Inside the NavbarComponent
the @Input
decorator is added, and a title
variable of type string
defined.
@Input() title: string;
Finally, inside the constructor
of the component, the Router
passed so it could be accessed by the component for navigation.
constructor( private router: Router) { }
Now, open the navbar.component.html
file located inside the navbar
directory.
You would see some html
code similar to this.
<div class="navbar-container">
<div class="logo-container">
<img width="80px" src="http://placehold.jp/18/ffffff/000000/120x120.png?text=LOGO">
</div>
<div class="links-container">
<a routerLink="" [ngClass]="{bold : title=='Teachers'}">Teachers</a>
<p>|</p>
<a routerLink="student" [ngClass]="{bold : title=='Students'}">Students</a>
</div>
<div class="blank-space"></div>
</div>
<div class="info-container">
<div class="logo-container">
<p class="info-text">{{title}}</p>
</div>
<div class="blank-space"></div>
<div class="links-container">
</div>
</div>
The navbar
contains 2 sections. One for displaying the navigation links (navbar-container) and one for displaying the title (info-container).
First, the logo is added to the navbar-container. The links-container includes the navigation links for our frontend.
routerLink
links the element to the route specified. On this instance, when the HTMLa
tag is clicked, it will navigate to the route specified atrouterLink
.[ngClass]
is aproperty binding method
inAngular
to add/remove a class to/from an element. Here, a logical operation is included inside the[ngClass]
. What happens here is, if thetitle
property passed in as an input for the element is equal to the specified name, the classbold
is added to thea
tag.
In the info-container we display the title
received using the {{ title }}
syntax.
The component add-new-teacher
can be found in the src/app/components
directory. It contains 4 files. Open the add-new-teacher.component.ts
file.
The file content will look like this.
import { Component, OnInit } from '@angular/core';
import {AppServiceService} from '../../app-service.service';
import { Router } from '@angular/router';
@Component({
selector: 'app-add-new-teacher',
templateUrl: './add-new-teacher.component.html',
styleUrls: ['./add-new-teacher.component.css']
})
export class AddNewTeacherComponent implements OnInit {
constructor(private service : AppServiceService, private router: Router) { }
ngOnInit(): void {
}
createTeacher(value){
const teacher = {
id : value.id,
name : value.name,
age : value.age
}
this.service.addTeacher(teacher).subscribe((response)=>{
this.router.navigate([''])
},(error)=>{
console.log('ERROR - ', error)
})
}
}
Here, inside the @Component
decorator there are some fields defined. Let's go through them one by one.
- selector - The component's CSS element selector.
- templateUrl - The location of the component's template file.
- styleUrls - The location of the component's private CSS styles.
Now let's understand the remaining content to implement the functionality of the add-new-teacher
component.
Let's understand the constructor of the AddNewTeacherComponent
.
constructor(private service : AppServiceService, private router: Router) { }
- Here we are passing the
AppServiceService
we exported under the section Services as a privateservice
instance. Also, we are passing aRouter
we exported in the section Routing as a privaterouter
instance.
Next, let's understand the method to create a teacher. Add the following line of code inside AddNewTeacherComponent
.
createTeacher(value) {
const teacher = {
id : value.id,
name : value.name,
age : value.age
}
this.service.addTeacher(teacher).subscribe((response)=>{
this.router.navigate([''])
},(error)=>{
console.log('ERROR - ', error)
})
}
Here, first a teacher object will be created using the value
object passed into the createTeacher
function.
By using the AppServiceService
instance service
passed into the constructor, the addTeacher
method of the service is called. The subscribe
method is used to receive the data asynchronously and update the website.
The
subscribe
method is used when the data is fetched from a remote server. Receiving data after requesting from a server tends to take some time. Because of this, when the code is read sequentially the function might not return the data immediately. Whenever the data is received it should update the frontend. To implement this functionality the frontend property assignments should be done asynchronously. This asynchronous updating mechanism is handled by thesubscribe
method.
The subscribe
method first catches the response
of the server if the request is successful. Then using the router
it navigates to the ''
route (home page) upon a successful post
request.
If an error occurs during the request, the error
is caught using the next callback function. In the code it is logged into the console.
Next, let's take look at the HTML template of the add-new-teacher
component. In the add-new-teacher
directory, look out for a file named add-new-teacher.component.html
and open it.
<app-navbar title="Add New Teacher"></app-navbar>
<div>
<form #addTeacherForm="ngForm" class="form-container" (ngSubmit)="createTeacher(addTeacherForm.value)">
<input id="teacher-id" ngModel name="id" type="text" placeholder="ID">
<input id="teacher-name" ngModel name="name" type="text" placeholder="Name">
<input id="teacher-age" ngModel name="age" type="text" placeholder="Age">
<button id="teacher-add" class="form-button">Create</button>
</form>
</div>
The navbar
component created in the Navigation section have been added at the top of the code with setting the title
to Add New Teacher.
Next, a form
HTML element is added to submit the data given provided by the user.
The component edit-teacher
can be found in the src/app/components
directory. It contains 4 files. Open the edit-teacher.component.ts
file.
import { Component, OnInit } from '@angular/core';
import { Router, NavigationExtras } from '@angular/router';
import {AppServiceService} from '../../app-service.service';
@Component({
selector: 'app-edit-teacher',
templateUrl: './edit-teacher.component.html',
styleUrls: ['./edit-teacher.component.css']
})
export class EditTeacherComponent implements OnInit {
teacherData: any;
constructor(private service : AppServiceService, private router: Router) { }
navigation = this.router.getCurrentNavigation();
ngOnInit(): void {
this.getTeacherData();
}
getTeacherData(){
let teacher = {
id : this.navigation.extras.state.id
}
this.service.getOneTeacherData(teacher).subscribe((response)=>{
this.teacherData = response[0];
},(error)=>{
console.log('ERROR - ', error)
})
}
editTeacher(values){
values.id = this.navigation.extras.state.id;
this.service.editTeacher(values).subscribe((response)=>{
this.teacherData = response[0];
},(error)=>{
console.log('ERROR - ', error)
})
}
}
Let's first check the ngOnInit
method.
ngOnInit(): void {
this.getTeacherData();
}
This will instruct the component to run the function getTeacherData
(which must be defined and explained below) during the initialization of the component.
Now, let's create the two methods to get the teachers details which is named getTeacherData
and a method to edit the Teacher data named editTeacher
.
getTeacherData(){
let teacher = {
id : this.navigation.extras.state.id
}
this.service.getOneTeacherData(teacher).subscribe((response)=>{
this.teacherData = response[0];
},(error)=>{
console.log('ERROR - ', error)
})
}
editTeacher(values){
values.id = this.navigation.extras.state.id;
this.service.editTeacher(values).subscribe((response)=>{
this.teacherData = response[0];
},(error)=>{
console.log('ERROR - ', error)
})
}
Here, firstly considering the function getTeacherData
we get the id of the teacher from the state object saved on the navigation.
By using the AppServiceService
instance service
passed into the constructor, the getOneTeacherData
method of the service is called. The subscribe
method is used to receive the data asynchronously.
Next, using the editTeacher
method we will update the details of a given teacher. Using the editTeacher
method the service is called.
Next, let's take look at the HTML template of the edit-teacher
component. In the edit-teacher
directory, look out for a file named edit-teacher.component.html
and open it.
<app-navbar title="Edit Teacher Details"></app-navbar>
<div>
<form #editTeacherForm="ngForm" class="form-container" (ngSubmit)="editTeacher(editTeacherForm.value)">
<input id="teacher-name" ngModel name="name" type="text" placeholder="Name" value="{{teacherData?.name}}">
<input id="teacher-age" ngModel name="age" type="text" placeholder="Age" value="{{teacherData?.age}}">
<button id="teacher-edit" class="form-button">Edit & Save</button>
</form>
</div>
The navbar
component have been added at the top of the code with setting the title
to Edit Teacher Details.
Next, a form
HTML element is added to submit the data given provided by the user.
In the form data the value
option is filled with the data received by the getTeacherData
function which is the teacherData
object.
The component teacher-table
can be found in the src/app/components
directory. It contains 4 files. Open the teacher-table.component.ts
file.
import { Component, OnInit } from '@angular/core';
import { Router, NavigationExtras } from '@angular/router';
import { faTrash, faPlus, faPenSquare } from '@fortawesome/free-solid-svg-icons';
import { AppServiceService } from '../../app-service.service';
@Component({
selector: 'app-teacher-table',
templateUrl: './teacher-table.component.html',
styleUrls: ['./teacher-table.component.css']
})
export class TeacherTableComponent implements OnInit {
faTrash = faTrash;
faPlus = faPlus;
faPenSquare = faPenSquare;
teacherData: any;
selected: any;
constructor(private service: AppServiceService, private router: Router) { }
ngOnInit(): void {
this.getTeacherData();
}
addNewTeacher() {
this.router.navigate(['addTeacher'])
}
editTeacher(id) {
const navigationExtras: NavigationExtras = {
state: {
id: id
}
};
this.router.navigate(['editTeacher'], navigationExtras)
}
getTeacherData() {
this.selected = 'Teachers';
this.service.getTeacherData().subscribe((response) => {
this.teacherData = Object.keys(response).map((key) => [response[key]]);
}, (error) => {
console.log('ERROR - ', error)
})
}
getStudentData() {
this.selected = 'Students';
this.service.getStudentData().subscribe((response) => {
this.teacherData = response;
}, (error) => {
console.log('ERROR - ', error)
})
}
search(value) {
}
deleteTeacher(itemid) {
const test = {
id: itemid
}
this.service.deleteTeacher(test).subscribe((response) => {
this.getTeacherData()
})
}
}
Let's now take a look at the ngOnInit
method of the Angular Component
.
ngOnInit(): void {
this.getTeacherData();
}
This instructs the component to run the function getTeacherData
(which must be defined and explained below) during the initialization of the component.
import { faTrash, faPlus, faPenSquare } from '@fortawesome/free-solid-svg-icons';
faTrash = faTrash;
faPlus = faPlus;
faPenSquare = faPenSquare;
faTrash
, faPlus
and faPenSqure
are icons that are imported from fontawesome package, these icons are used in the teacher-table.component.html
file.
Now, let's take a look at the 4 methods in TeacherTableComponent
to get data for the component and route to specific functions on the system.
addNewTeacher
-> Route to add new teacher component.editTeacher
-> Route to add edit teacher component.getTeacherData
-> Get the data from the list of teachers in the system.deleteTeacher
-> Delete the teacher with the id that is passed.
Here the
search
function is not implemented. As a task of the Capstone Project you need to implement the logic tosearch
a teacher by a given text field. You should initialize and update theteacherData
array with the searched results.
Next, let's take look at the HTML template of the teacher-table
component. In the teacher-table
directory, look out for a file named teacher-table.component.html
and open it.
<app-navbar title="Teachers"></app-navbar>
<div class="add-btn-container">
<div class="add-btn-elements-container">
<input id="teacher-search" style="height: 20px;" #box (keyup)="search(box.value)" placeholder="Search">
<button style="width: 120px; height: 43px; font-size: 14px;" (click)="addNewTeacher()" class="btn">Add New <fa-icon [icon]="faPlus"></fa-icon></button>
</div>
</div>
<div class="table-container">
<table id="teacher-table">
<tr>
<th>Name</th>
<th>Staff ID</th>
<th>DOB</th>
<th style="width: 50px;"></th>
<th style="width: 50px;"></th>
</tr>
<tr *ngFor="let teacher of teacherData">
<td>{{teacher[0].name}}</td>
<td>{{teacher[0].id}}</td>
<td>{{2022 - teacher[0].age}}</td>
<td id="teacher-edit-{{teacher[0].id}}" (click)="editTeacher(teacher[0].id)" style="text-align: center;">
<fa-icon class="edit-icon" [icon]="faPenSquare"></fa-icon>
</td>
<td id="teacher-delete-{{teacher[0].id}}" (click)="deleteTeacher(teacher[0].id)"
style="text-align: center; color: #FC4F4F;">
<fa-icon class="trash-icon" [icon]="faTrash"></fa-icon>
</td>
</tr>
</table>
</div>
The navbar
component have been added at the top of the code with setting the title
to Teachers.
Check the <input>
tag under the <div class="add-btn-elements-container">
tag. This controls the table search functionality. If you enter a value for the input tag it will run the search(box.value)
function which will search the teachers using the passed text.
Next, a button
is added to add a teacher which on click execute the addNewTeacher()
function defined in the component file.
Note the usage of faTrash
,faPlus
and faPenSquare
which are the imported icons from the awesome package mentioned above.
Next, a table
,<tr>
and <td>
HTML elements are used to create a table and *ngFor="let teacher of teacherData"
is used to loop over teacherData where each item is accessed as teacher variable during the loop. the teacher variable is used to access the row data.