Let's expand the app further by adding:
- Edit Employee Functionality: Allows users to update employee information.
- Admin Access Control: Protect certain actions like editing or deleting employees so only admins can perform them.
- More Detailed Form Validation: Improve form validation for fields like passwords, emails, and other inputs.
Step 1: Edit Employee Functionality
1.1. Create Edit Employee Page
Generate a new page for editing an employee:
ionic generate page edit-employee
1.2. Edit Employee Form
In edit-employee.page.html, create the form to edit employee details.
<ion-header>
<ion-toolbar>
<ion-title>Edit Employee</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<form [formGroup]="editForm" (ngSubmit)="updateEmployee()">
<ion-item>
<ion-label position="floating">Name</ion-label>
<ion-input type="text" formControlName="name"></ion-input>
</ion-item>
<ion-note *ngIf="editForm.get('name').hasError('required') && editForm.get('name').touched">
Name is required
</ion-note>
<ion-item>
<ion-label position="floating">Email</ion-label>
<ion-input type="email" formControlName="email"></ion-input>
</ion-item>
<ion-note *ngIf="editForm.get('email').hasError('required') && editForm.get('email').touched">
Email is required
</ion-note>
<ion-note *ngIf="editForm.get('email').hasError('email') && editForm.get('email').touched">
Invalid email
</ion-note>
<ion-item>
<ion-label position="floating">Position</ion-label>
<ion-input type="text" formControlName="position"></ion-input>
</ion-item>
<ion-note *ngIf="editForm.get('position').hasError('required') && editForm.get('position').touched">
Position is required
</ion-note>
<ion-button expand="block" type="submit" [disabled]="editForm.invalid">Update Employee</ion-button>
</form>
</ion-content>
1.3. Edit Employee Logic
In edit-employee.page.ts, handle the form submission and update logic:
// src/app/pages/edit-employee/edit-employee.page.ts
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { EmployeeService } from '../../services/employee.service';
@Component({
selector: 'app-edit-employee',
templateUrl: './edit-employee.page.html',
styleUrls: ['./edit-employee.page.scss'],
})
export class EditEmployeePage implements OnInit {
editForm: FormGroup;
employeeId: number;
constructor(
private fb: FormBuilder,
private employeeService: EmployeeService,
private route: ActivatedRoute,
private router: Router
) {}
ngOnInit() {
this.employeeId = +this.route.snapshot.paramMap.get('id');
this.initializeForm();
this.loadEmployeeData();
}
initializeForm() {
this.editForm = this.fb.group({
name: ['', [Validators.required]],
email: ['', [Validators.required, Validators.email]],
position: ['', [Validators.required]],
});
}
async loadEmployeeData() {
const token = localStorage.getItem('token');
try {
const employee = await this.employeeService.getEmployeeById(token, this.employeeId);
this.editForm.patchValue(employee); // Populate the form with employee data
} catch (error) {
console.log('Error loading employee data:', error);
}
}
async updateEmployee() {
if (this.editForm.valid) {
const token = localStorage.getItem('token');
const employeeData = this.editForm.value;
try {
await this.employeeService.updateEmployee(token, this.employeeId, employeeData);
this.router.navigate(['/employee-list']); // Redirect to employee list after successful update
} catch (error) {
console.log('Error updating employee:', error);
}
}
}
}
1.4. Add Method to Employee Service
In employee.service.ts, add a method to fetch employee by ID:
// src/app/services/employee.service.ts
async getEmployeeById(token: string, employeeId: number) {
const response = await axios.get(`${this.apiUrl}/${employeeId}`, {
headers: { Authorization: `Bearer ${token}` },
});
return response.data;
}
Update the app-routing.module.ts to include a route for editing employees:
// src/app/app-routing.module.ts
const routes: Routes = [
{ path: 'login', loadChildren: () => import('./pages/login/login.module').then(m => m.LoginPageModule) },
{ path: 'employee-list', loadChildren: () => import('./pages/employee-list/employee-list.module').then(m => m.EmployeeListPageModule) },
{ path: 'edit-employee/:id', loadChildren: () => import('./pages/edit-employee/edit-employee.module').then(m => m.EditEmployeePageModule) },
{ path: '', redirectTo: '/login', pathMatch: 'full' },
];
Step 2: Admin Access Control
To restrict certain actions like editing and deleting employees to admins only, we’ll update the backend and frontend.
2.1. Backend - Update Routes for Role-Based Access
In backend/utils/verifyToken.ts, ensure that certain routes are accessible only by users with the "Admin" role.
// backend/utils/verifyToken.ts
export const verifyRole = (handler, role) => async (req: NextApiRequest, res: NextApiResponse) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({ message: 'No token provided' });
}
try {
const decoded = verifyToken(token);
if (decoded.role !== role) {
return res.status(403).json({ message: 'Forbidden' });
}
req.user = decoded;
return handler(req, res);
} catch (error) {
return res.status(401).json({ message: 'Invalid token' });
}
};
For example, modify the employee deletion route so that only admins can delete employees:
// backend/pages/api/employees/[id].ts
import { verifyRole } from '../../../utils/verifyToken';
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const employeeRepository = AppDataSource.getRepository(Employee);
const { id } = req.query;
if (req.method === 'DELETE') {
const employee = await employeeRepository.findOne({ where: { id: Number(id) } });
if (!employee) {
return res.status(404).json({ message: 'Employee not found' });
}
await employeeRepository.remove(employee);
res.status(204).send();
}
};
export default verifyRole(handler, 'Admin');
2.2. Frontend - Display Admin-Only Actions
In employee-list.page.html, only show the "Delete" button if the logged-in user is an admin.
<ion-item *ngFor="let employee of employees">
<ion-label>{{ employee.name }} ({{ employee.position }})</ion-label>
<ion-button color="primary" (click)="editEmployee(employee.id)">Edit</ion-button>
<ion-button *ngIf="isAdmin" color="danger" (click)="deleteEmployee(employee.id)">Delete</ion-button>
</ion-item>
In employee-list.page.ts, check if the user has the "Admin" role:
// src/app/pages/employee-list/employee-list.page.ts
isAdmin = false;
async ngOnInit() {
const token = localStorage.getItem('token');
const decodedToken = this.decodeToken(token);
this.isAdmin = decodedToken.role === 'Admin'; // Check if user is an Admin
this.loadEmployees();
}
decodeToken(token: string) {
// Decode JWT token (you may use a library like jwt-decode here)
const base64Url = token.split('.')[1];
const base64 = base64Url.replace('-', '+').replace('_', '/');
return JSON.parse(atob(base64));
}
Step 3: More Detailed Form Validation
We’ll improve validation rules for different fields.
3.1. Add Password Validation to Registration and Edit Forms
For password fields, we can enforce stronger rules (e.g., minimum 8 characters, must contain numbers, etc.). In edit-employee.page.ts:
this.editForm = this.fb.group({
name: ['', [Validators.required]],
email: ['', [Validators.required, Validators.email]],
position: ['', [Validators.required]],
password: ['', [Validators.minLength(8), Validators.pattern('^(?=.*[0-9])(?=.*[a-zA-Z])([a-zA-Z0-9]+)$')]],
});
Show validation errors in the template:
<ion-item>
<ion-label position="floating">Password</ion-label>
<ion-input type="password" form
ControlName="password"></ion-input>
</ion-item>
<ion-note *ngIf="editForm.get('password').hasError('minlength') && editForm.get('password').touched">
Password must be at least 8 characters
</ion-note>
<ion-note *ngIf="editForm.get('password').hasError('pattern') && editForm.get('password').touched">
Password must contain both letters and numbers
</ion-note>
3.2. Enforce Validation Rules in Backend
In the backend, use class-validator to enforce validation rules for fields. In Employee.ts:
@Column()
@IsNotEmpty({ message: 'Password is required' })
@MinLength(8, { message: 'Password must be at least 8 characters' })
@Matches(/^(?=.*[0-9])(?=.*[a-zA-Z])([a-zA-Z0-9]+)$/, {
message: 'Password must contain both letters and numbers',
})
password: string;
Final Thoughts
With these changes, we have:
- Edit functionality: Employees can be updated through a dedicated form.
- Admin access control: Only admins can delete employees, with appropriate UI restrictions.
- More detailed form validation: Stronger validation rules for input fields like passwords and email.
This application is now more robust, secure, and user-friendly!