add better validation to forms

This commit is contained in:
Jan-Marlon Leibl 2024-12-03 09:20:59 +01:00
parent 074da034c5
commit 6e5eadd3a2
8 changed files with 86 additions and 18 deletions

View File

@ -2,6 +2,8 @@ import { Routes } from '@angular/router';
import { HotelDetailsComponent } from './hotel-details/hotel-details.component';
import { HotelListComponent } from './hotel-list/hotel-list.component';
import { HotelFormComponent } from './hotel-form/hotel-form.component';
import { inject } from '@angular/core';
import { formGuard } from './form.guard';
export const routes: Routes = [
{
@ -11,9 +13,11 @@ export const routes: Routes = [
{
path: "hotels/:id",
component: HotelDetailsComponent,
canDeactivate: [formGuard],
},
{
path: "create-hotel",
component: HotelFormComponent,
canDeactivate: [formGuard],
},
];

View File

@ -0,0 +1,17 @@
import { TestBed } from '@angular/core/testing';
import { CanDeactivateFn } from '@angular/router';
import { formGuard } from './form.guard';
describe('formGuard', () => {
const executeGuard: CanDeactivateFn = (...guardParameters) =>
TestBed.runInInjectionContext(() => formGuard(...guardParameters));
beforeEach(() => {
TestBed.configureTestingModule({});
});
it('should be created', () => {
expect(executeGuard).toBeTruthy();
});
});

15
src/app/form.guard.ts Normal file
View File

@ -0,0 +1,15 @@
import { CanDeactivateFn } from '@angular/router';
import { HotelFormComponent } from './hotel-form/hotel-form.component';
import { HotelDetailsComponent } from './hotel-details/hotel-details.component';
export const formGuard: CanDeactivateFn<HotelFormComponent | HotelDetailsComponent> = (component: HotelFormComponent | HotelDetailsComponent, currentRoute, currentState, nextState) => {
console.log(component)
if (component instanceof HotelFormComponent && component.form?.dirty) {
return confirm('You have unsaved changes. Do you really want to leave?');
}
else if (component instanceof HotelDetailsComponent && component.hotelForm?.form?.dirty) {
return confirm('You have unsaved changes. Do you really want to leave?');
}
return true;
};

View File

@ -1,5 +1,5 @@
<div class="container p-4 mx-auto max-w-4xl">
<div class="border border-gray-500 p-4 rounded mt-4" *ngIf="hotel">
<app-hotel-form [hotel]="hotel"></app-hotel-form>
<div *ngIf="hotel">
<app-hotel-form [hotel]="hotel" #hotelForm></app-hotel-form>
</div>
</div>

View File

@ -1,23 +1,24 @@
import { HttpClient } from '@angular/common/http';
import { Component, OnInit } from '@angular/core';
import {ActivatedRoute, Router, RouterLink} from '@angular/router';
import { Component, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Hotel } from '../HotelItem/hotel';
import {CurrencyPipe, NgIf} from '@angular/common';
import { StarRatingComponent } from '../star-rating/star-rating.component';
import { HotelItem } from '../HotelItem/HotelItem.component';
import { NgIf } from '@angular/common';
import { catchError, EMPTY } from 'rxjs';
import {HotelFormComponent} from "../hotel-form/hotel-form.component";
import { HotelFormComponent } from "../hotel-form/hotel-form.component";
@Component({
selector: 'app-hotel-details',
standalone: true,
imports: [CurrencyPipe, StarRatingComponent, HotelItem, HotelFormComponent, NgIf, RouterLink],
imports: [HotelFormComponent, NgIf],
templateUrl: './hotel-details.component.html',
styleUrl: './hotel-details.component.css'
})
export class HotelDetailsComponent implements OnInit {
public hotel: any;
@ViewChild('hotelForm', { static: false })
public hotelForm!: HotelFormComponent;
constructor(private route: ActivatedRoute, private http: HttpClient, private router: Router) { }
ngOnInit(): void {

View File

@ -57,7 +57,7 @@
<mat-icon class="text-red-500">delete</mat-icon>
</button>
</div>
<button type="button" mat-stroked-button (click)="addTag()" class="mt-2">Add Tag</button>
<button type="button" mat-button (click)="addTag()" class="mt-2">Add Tag</button>
</div>
</div>
<div class="flex justify-between">
@ -65,8 +65,19 @@
<button type="submit" mat-flat-button (click)="submit()">Submit</button>
<button type="button" mat-stroked-button routerLink="/">Cancel</button>
</div>
<button mat-icon-button aria-label="Delete hotel" *ngIf="hotel" (click)="delete(hotel.id)">
<mat-icon class="text-red-500">delete</mat-icon>
<button mat-stroked-button color="warn" aria-label="Delete hotel" *ngIf="hotel" (click)="showDeleteConfirmation = true">
Remove
</button>
<div *ngIf="showDeleteConfirmation" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div class="bg-white p-6 rounded-lg shadow-lg max-w-sm w-full">
<h2 class="text-xl font-bold mb-4">Confirm Deletion</h2>
<p class="mb-6">Are you sure you want to delete this hotel?</p>
<div class="flex justify-end space-x-2">
<button mat-stroked-button (click)="showDeleteConfirmation = false">Cancel</button>
<button mat-flat-button color="warn" (click)="delete(hotel.id)">Delete</button>
</div>
</div>
</div>
</div>
</form>

View File

@ -1,4 +1,4 @@
import { Component, inject, Inject, Input } from '@angular/core';
import { Component, HostListener, inject, Inject, Input } from '@angular/core';
import { FormArray, FormControl, FormGroup, ReactiveFormsModule, Validators } from "@angular/forms";
import { Hotel } from "../HotelItem/hotel";
import { HttpClient } from '@angular/common/http';
@ -32,6 +32,8 @@ export class HotelFormComponent {
public router: Router = inject(Router);
public showDeleteConfirmation = false;
constructor(private http: HttpClient) {}
ngOnInit() {
@ -66,7 +68,7 @@ export class HotelFormComponent {
}
public submit() {
if (this.form.valid) {
if (this.form.valid && !this.form.dirty) {
const hotelData: Hotel = {
id: this.hotel?.id || 0,
hotelName: this.form.value.name,

View File

@ -1,10 +1,28 @@
<div class="container p-4 mx-auto max-w-4xl">
<h1>{{'hello' | uppercase | text}}</h1>
<app-search [(input)]="search"></app-search>
<button routerLink="/create-hotel" mat-flat-button class="btn btn-primary bg-blue-500 text-white font-semibold py-2 px-4 rounded-md shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-opacity-75 my-4">Create New Hotel</button>
<div *ngIf="hotels[0].hotelName">
<div class="fixed top-0 left-0 w-full bg-white/80 backdrop-blur-xl z-50">
<div class="justify-center max-w-4xl container p-4 mx-auto">
<h1>{{'hello' | uppercase | text}}</h1>
<app-search [(input)]="search"></app-search>
<button routerLink="/create-hotel" mat-flat-button
class="btn btn-primary bg-blue-500 text-white font-semibold py-2 px-4 rounded-md shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-opacity-75 my-4">Create
New Hotel</button>
</div>
</div>
<div *ngIf="hotels[0].hotelName" class="mt-56">
<div *ngFor="let hotel of hotels">
<app-hotel-item *ngIf="hotel.hotelName.toLowerCase().includes(search.toLowerCase())" [hotel]="hotel"></app-hotel-item>
</div>
</div>
<div *ngIf="!hotels[0].hotelName" class="mt-56">
<div class="animate-pulse space-y-4">
<div class="h-8 bg-gray-200 rounded w-52"></div>
<div class="space-y-3">
<div class="h-4 bg-gray-200 rounded w-3/4"></div>
<div class="h-4 bg-gray-200 rounded w-11/12"></div>
<div class="h-4 bg-gray-200 rounded w-2/3"></div>
</div>
<div class="h-[300px] bg-gray-200 rounded w-full"></div>
</div>
</div>
</div>