diff --git a/angular.json b/angular.json
index 0529da2..9e3a815 100644
--- a/angular.json
+++ b/angular.json
@@ -28,6 +28,7 @@
"src/assets"
],
"styles": [
+ "@angular/material/prebuilt-themes/azure-blue.css",
"src/styles.css"
],
"scripts": []
@@ -86,6 +87,7 @@
}
],
"styles": [
+ "@angular/material/prebuilt-themes/azure-blue.css",
"src/styles.css"
],
"scripts": []
diff --git a/bun.lockb b/bun.lockb
index 1c972ff..ac14009 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/package-lock.json b/package-lock.json
index 0dac9dc..21da74a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,13 +9,16 @@
"version": "0.0.0",
"dependencies": {
"@angular/animations": "^18.2.3",
+ "@angular/cdk": "^18.2.3",
"@angular/common": "^18.2.3",
"@angular/compiler": "^18.2.3",
"@angular/core": "^18.2.3",
"@angular/forms": "^18.2.3",
+ "@angular/material": "^18.2.3",
"@angular/platform-browser": "^18.2.3",
"@angular/platform-browser-dynamic": "^18.2.3",
"@angular/router": "^18.2.3",
+ "angular-in-memory-web-api": "^0.18.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.14.3"
@@ -385,6 +388,23 @@
"node": ">=10"
}
},
+ "node_modules/@angular/cdk": {
+ "version": "18.2.3",
+ "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-18.2.3.tgz",
+ "integrity": "sha512-lUcpYTxPZuntJ1FK7V2ugapCGMIhT6TUDjIGgXfS9AxGSSKgwr8HNs6Ze9pcjYC44UhP40sYAZuiaFwmE60A2A==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.3.0"
+ },
+ "optionalDependencies": {
+ "parse5": "^7.1.2"
+ },
+ "peerDependencies": {
+ "@angular/common": "^18.0.0 || ^19.0.0",
+ "@angular/core": "^18.0.0 || ^19.0.0",
+ "rxjs": "^6.5.3 || ^7.4.0"
+ }
+ },
"node_modules/@angular/cli": {
"version": "18.2.3",
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-18.2.3.tgz",
@@ -531,6 +551,24 @@
"rxjs": "^6.5.3 || ^7.4.0"
}
},
+ "node_modules/@angular/material": {
+ "version": "18.2.3",
+ "resolved": "https://registry.npmjs.org/@angular/material/-/material-18.2.3.tgz",
+ "integrity": "sha512-JFfvXaMHMhskncaxxus4sDvie9VYdMkfYgfinkLXpZlPFyn1IzjDw0c1BcrcsuD7UxQVZ/v5tucCgq1FQfGRpA==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.3.0"
+ },
+ "peerDependencies": {
+ "@angular/animations": "^18.0.0 || ^19.0.0",
+ "@angular/cdk": "18.2.3",
+ "@angular/common": "^18.0.0 || ^19.0.0",
+ "@angular/core": "^18.0.0 || ^19.0.0",
+ "@angular/forms": "^18.0.0 || ^19.0.0",
+ "@angular/platform-browser": "^18.0.0 || ^19.0.0",
+ "rxjs": "^6.5.3 || ^7.4.0"
+ }
+ },
"node_modules/@angular/platform-browser": {
"version": "18.2.3",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-18.2.3.tgz",
@@ -4763,6 +4801,20 @@
"ajv": "^8.8.2"
}
},
+ "node_modules/angular-in-memory-web-api": {
+ "version": "0.18.0",
+ "resolved": "https://registry.npmjs.org/angular-in-memory-web-api/-/angular-in-memory-web-api-0.18.0.tgz",
+ "integrity": "sha512-Eqkr9+x3d7K4dmn6Qs3ZVAfqBDPZN0N7Qel5i8eU/pe5r44J/pfxlNW+1LC2Sb2PdENEdvFzC8wx8qly5+kQyQ==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.3.0"
+ },
+ "peerDependencies": {
+ "@angular/common": "^18.0.0",
+ "@angular/core": "^18.0.0",
+ "rxjs": "^6.5.3 || ^7.4.0"
+ }
+ },
"node_modules/ansi-colors": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
@@ -6487,7 +6539,7 @@
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
- "dev": true,
+ "devOptional": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
@@ -10467,7 +10519,7 @@
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
"integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"dependencies": {
"entities": "^4.4.0"
diff --git a/package.json b/package.json
index 0671431..01682cb 100644
--- a/package.json
+++ b/package.json
@@ -11,10 +11,12 @@
"private": true,
"dependencies": {
"@angular/animations": "^18.2.3",
+ "@angular/cdk": "^18.2.3",
"@angular/common": "^18.2.3",
"@angular/compiler": "^18.2.3",
"@angular/core": "^18.2.3",
"@angular/forms": "^18.2.3",
+ "@angular/material": "^18.2.3",
"@angular/platform-browser": "^18.2.3",
"@angular/platform-browser-dynamic": "^18.2.3",
"@angular/router": "^18.2.3",
@@ -39,4 +41,4 @@
"tailwindcss": "^3.4.10",
"typescript": "~5.5.2"
}
-}
+}
\ No newline at end of file
diff --git a/src/app/Button/button.component.html b/src/app/Button/button.component.html
deleted file mode 100644
index 54fb491..0000000
--- a/src/app/Button/button.component.html
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/src/app/Button/button.component.ts b/src/app/Button/button.component.ts
deleted file mode 100644
index c77daef..0000000
--- a/src/app/Button/button.component.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { Component } from "@angular/core";
-
-
-@Component({
- selector: 'app-button',
- standalone: true,
- templateUrl: './button.component.html',
-})
-export class ButtonComponent {
- public text = 'Test';
-}
diff --git a/src/app/Child/child.component.html b/src/app/Child/child.component.html
deleted file mode 100644
index 84f430a..0000000
--- a/src/app/Child/child.component.html
+++ /dev/null
@@ -1,4 +0,0 @@
-
Child
-Balance: {{balance}}
-I'm broke. Now I'm about as poor as Jan-Marlon.
-
diff --git a/src/app/Child/child.component.ts b/src/app/Child/child.component.ts
deleted file mode 100644
index e03c3ad..0000000
--- a/src/app/Child/child.component.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { Component, Input, Output, EventEmitter } from "@angular/core";
-
-@Component({
- selector: 'app-child',
- standalone: true,
- templateUrl: './child.component.html',
-})
-export class ChildComponent {
- @Input() public balance: number = 0;
- @Output() balanceChange = new EventEmitter();
-
- public isBroke = false;
-
- public takeMoney() {
- if (this.balance <= 0) {
- this.isBroke = true;
- } else {
- this.balance -= 50;
- if (this.balance == 0) {
- this.isBroke = true;
- }
- this.balanceChange.emit(this.balance);
- }
- }
-}
diff --git a/src/app/Parent/parent.component.html b/src/app/Parent/parent.component.html
deleted file mode 100644
index 6fc429c..0000000
--- a/src/app/Parent/parent.component.html
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
Parent
-
Kontostand: {{ balance }}
-
-
-
-
-
-
diff --git a/src/app/Parent/parent.component.ts b/src/app/Parent/parent.component.ts
deleted file mode 100644
index 502bb94..0000000
--- a/src/app/Parent/parent.component.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { Component } from "@angular/core";
-import { ChildComponent } from "../Child/child.component";
-
-
-@Component({
- selector: 'app-parent',
- standalone: true,
- templateUrl: './parent.component.html',
- imports: [ChildComponent],
-})
-export class ParentComponent {
- public balance = 1000;
-
- public addFifty() {
- this.balance += 50;
- }
-}
diff --git a/src/app/Parent/services/hotel.service.ts b/src/app/Parent/services/hotel.service.ts
deleted file mode 100644
index 2eb1125..0000000
--- a/src/app/Parent/services/hotel.service.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-import { Injectable } from "@angular/core";
-import { Hotel } from "../../HotelItem/hotel";
-import { from, Observable } from "rxjs";
-
-@Injectable()
-export class HotelService {
- public getHotels(): Observable {
- return from([
- {
- "id": 1,
- "hotelName": "Buea süßes Leben",
- "description": "Schöne Aussicht am Meer",
- "price": 230.5,
- "imageUrl": "assets/img/1.jpg",
- "rating": 3.5,
- "tags": ["test"]
- },
- {
- "id": 2,
- "hotelName": "Marrakesch",
- "description": "Genießen Sie den Blick auf die Berge",
- "price": 145.5,
- "imageUrl": "assets/img/2.jpg",
- "rating": 5,
- "tags": ["test"]
- },
- {
- "id": 3,
- "hotelName": "Abuja neuer Palast",
- "description": "Kompletter Aufenthalt mit Autoservice",
- "price": 120.12,
- "imageUrl": "assets/img/3.jpg",
- "rating": 4,
- "tags": ["test"]
- },
- {
- "id": 4,
- "hotelName": "Kapstadt Stadt",
- "description": "Wunderschönes Ambiente für Ihren Aufenthalt",
- "price": 135.12,
- "imageUrl": "assets/img/4.jpg",
- "rating": 2.5,
- "tags": ["test"]
- }
- ]);
- }
-}
diff --git a/src/app/Search/search.component.html b/src/app/Search/search.component.html
index a408279..150b096 100644
--- a/src/app/Search/search.component.html
+++ b/src/app/Search/search.component.html
@@ -1 +1,2 @@
-
+
+
diff --git a/src/app/Search/search.component.ts b/src/app/Search/search.component.ts
index c3564a0..2c9bdb5 100644
--- a/src/app/Search/search.component.ts
+++ b/src/app/Search/search.component.ts
@@ -1,7 +1,5 @@
-import { CommonModule } from "@angular/common";
-import { Component, NgModule } from "@angular/core";
-import { FormsModule, NgForm, NgModel } from "@angular/forms";
-import { Input, Output } from "@angular/core";
+import { Component, Input, Output } from "@angular/core";
+import { FormsModule } from "@angular/forms";
import { EventEmitter } from "@angular/core";
@Component({
diff --git a/src/app/api/api.ts b/src/app/api/api.ts
index b330010..6aac176 100644
--- a/src/app/api/api.ts
+++ b/src/app/api/api.ts
@@ -1,6 +1,6 @@
import { InMemoryDbService } from 'angular-in-memory-web-api';
-import { Hotel } from '../HotelItem/hotel';
+import { Hotel } from '../hotel-item/hotel';
/**
* Initial data for in memory web api
diff --git a/src/app/app.component.html b/src/app/app.component.html
index 346d4ad..90c6b64 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -1,5 +1 @@
-
-
-
-
-
+
\ No newline at end of file
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index 50508e2..8725472 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -1,20 +1,12 @@
import { Component, Injectable } from '@angular/core';
-import { HotelItem } from './HotelItem/HotelItem.component';
-import { SearchComponent } from './Search/search.component';
-import { AsyncPipe, NgFor, NgForOf, NgIf, UpperCasePipe } from '@angular/common';
-import { TextPipe } from '../text.pipe';
-import { HotelService } from './Parent/services/hotel.service';
-import { inject } from '@angular/core';
-import { filter, from, last, map, Observable, scan } from 'rxjs';
-import { HttpClient } from '@angular/common/http';
-import { Hotel } from './HotelItem/hotel';
+import { HotelService } from './service/hotel.service';
import { RouterOutlet } from '@angular/router';
@Injectable({providedIn: "root"})
@Component({
selector: 'app-root',
standalone: true,
- imports: [RouterOutlet, NgFor, NgForOf, NgIf, HotelItem, SearchComponent, UpperCasePipe, TextPipe, AsyncPipe],
+ imports: [RouterOutlet],
templateUrl: './app.component.html',
providers: [HotelService],
styleUrl: './app.component.css'
diff --git a/src/app/app.config.ts b/src/app/app.config.ts
index 55b7251..db633ab 100644
--- a/src/app/app.config.ts
+++ b/src/app/app.config.ts
@@ -8,6 +8,7 @@ import { registerLocaleData } from '@angular/common';
import { provideHttpClient } from '@angular/common/http';
import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
import { HotelData } from './api/api';
+import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
registerLocaleData(localeDe, 'de-DE');
registerLocaleData(localeCn, 'cn-CN');
@@ -16,5 +17,5 @@ export const appConfig: ApplicationConfig = {
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes),
provideHttpClient(),
- importProvidersFrom(InMemoryWebApiModule.forRoot(HotelData))]
+ importProvidersFrom(InMemoryWebApiModule.forRoot(HotelData)), provideAnimationsAsync()]
};
diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts
index eaaae27..f64b610 100644
--- a/src/app/app.routes.ts
+++ b/src/app/app.routes.ts
@@ -1,6 +1,9 @@
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 { formGuard } from './form.guard';
+import { NotFoundComponent } from './not-found/not-found.component';
export const routes: Routes = [
{
@@ -10,5 +13,15 @@ export const routes: Routes = [
{
path: "hotels/:id",
component: HotelDetailsComponent,
+ canDeactivate: [formGuard],
+ },
+ {
+ path: "create-hotel",
+ component: HotelFormComponent,
+ canDeactivate: [formGuard],
+ },
+ {
+ path: "**",
+ component: NotFoundComponent,
},
];
diff --git a/src/app/form.guard.spec.ts b/src/app/form.guard.spec.ts
new file mode 100644
index 0000000..1c3d01f
--- /dev/null
+++ b/src/app/form.guard.spec.ts
@@ -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();
+ });
+});
diff --git a/src/app/form.guard.ts b/src/app/form.guard.ts
new file mode 100644
index 0000000..60b62b7
--- /dev/null
+++ b/src/app/form.guard.ts
@@ -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 = (component: HotelFormComponent | HotelDetailsComponent, currentRoute, currentState, nextState) => {
+ const hasUnsavedChanges =
+ (component instanceof HotelFormComponent && component.form?.dirty) ||
+ (component instanceof HotelDetailsComponent && component.hotelForm?.form?.dirty);
+
+ if (hasUnsavedChanges) {
+ return confirm('You have unsaved changes. Do you really want to leave?');
+ }
+
+ return true;
+};
diff --git a/src/app/hotel-details/hotel-details.component.html b/src/app/hotel-details/hotel-details.component.html
index 3a1c644..9f26099 100644
--- a/src/app/hotel-details/hotel-details.component.html
+++ b/src/app/hotel-details/hotel-details.component.html
@@ -1 +1,5 @@
-
+
diff --git a/src/app/hotel-details/hotel-details.component.ts b/src/app/hotel-details/hotel-details.component.ts
index 2d3c396..5721836 100644
--- a/src/app/hotel-details/hotel-details.component.ts
+++ b/src/app/hotel-details/hotel-details.component.ts
@@ -1,22 +1,24 @@
import { HttpClient } from '@angular/common/http';
-import { Component, OnInit } from '@angular/core';
+import { Component, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
-import { Hotel } from '../HotelItem/hotel';
-import { CurrencyPipe } from '@angular/common';
-import { StarRatingComponent } from '../star-rating/star-rating.component';
-import { HotelItem } from '../HotelItem/HotelItem.component';
+import { Hotel } from '../hotel-item/hotel';
+import { NgIf } from '@angular/common';
import { catchError, EMPTY } from 'rxjs';
+import { HotelFormComponent } from "../hotel-form/hotel-form.component";
@Component({
selector: 'app-hotel-details',
standalone: true,
- imports: [CurrencyPipe, StarRatingComponent, HotelItem],
+ 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 {
diff --git a/src/app/hotel-form/hotel-form.component.css b/src/app/hotel-form/hotel-form.component.css
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/hotel-form/hotel-form.component.html b/src/app/hotel-form/hotel-form.component.html
new file mode 100644
index 0000000..64b99a8
--- /dev/null
+++ b/src/app/hotel-form/hotel-form.component.html
@@ -0,0 +1,70 @@
+
\ No newline at end of file
diff --git a/src/app/hotel-form/hotel-form.component.spec.ts b/src/app/hotel-form/hotel-form.component.spec.ts
new file mode 100644
index 0000000..3f1f5bd
--- /dev/null
+++ b/src/app/hotel-form/hotel-form.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { HotelFormComponent } from './hotel-form.component';
+
+describe('HotelFormComponent', () => {
+ let component: HotelFormComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [HotelFormComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(HotelFormComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/hotel-form/hotel-form.component.ts b/src/app/hotel-form/hotel-form.component.ts
new file mode 100644
index 0000000..5998b0e
--- /dev/null
+++ b/src/app/hotel-form/hotel-form.component.ts
@@ -0,0 +1,150 @@
+import { Component, inject, Input } from '@angular/core';
+import { AbstractControl, FormArray, FormControl, FormGroup, ReactiveFormsModule, Validators } from "@angular/forms";
+import { Hotel } from "../hotel-item/hotel";
+import { HttpClient } from '@angular/common/http';
+import { catchError, debounceTime } from "rxjs";
+import { Router, RouterLink } from "@angular/router";
+import { NgClass, NgFor, NgIf } from "@angular/common";
+import { MatButtonModule } from "@angular/material/button";
+import { MatIconModule } from "@angular/material/icon";
+import { FormsModule } from '@angular/forms';
+
+@Component({
+ selector: 'app-hotel-form',
+ standalone: true,
+ imports: [
+ ReactiveFormsModule,
+ RouterLink,
+ NgIf,
+ MatButtonModule,
+ MatIconModule,
+ NgFor,
+ FormsModule,
+ NgClass
+ ],
+ templateUrl: './hotel-form.component.html',
+ styleUrl: './hotel-form.component.css'
+})
+export class HotelFormComponent {
+ public form!: FormGroup;
+
+ @Input()
+ public hotel!: Hotel;
+
+ public router: Router = inject(Router);
+
+ public showDeleteConfirmation = false;
+
+ constructor(private http: HttpClient) { }
+
+ public errorMessage: string = '';
+
+ ngOnInit() {
+ this.form = new FormGroup({
+ name: new FormControl(this.hotel?.hotelName || '', [Validators.minLength(3), Validators.required]),
+ description: new FormControl(this.hotel?.description || '', [Validators.minLength(3), Validators.required]),
+ price: new FormControl(this.hotel?.price || '', [Validators.min(0), Validators.required]),
+ rating: new FormControl(this.hotel?.rating || '', [Validators.min(0), Validators.max(5), Validators.required]),
+ tags: new FormArray(this.hotel?.tags?.map(tag => new FormControl(tag)) || [])
+ });
+
+ Object.keys(this.form.controls).forEach(key => {
+ const control = this.form.get(key);
+ control?.valueChanges.pipe(
+ debounceTime(1000)
+ ).subscribe(() => {
+ this.setError(control as AbstractControl, key);
+ });
+ });
+ }
+
+ public delete() {
+ this.http.delete(`api/hotels/${this.hotel?.id}`).pipe(
+ catchError(err => {
+ console.error('Error deleting hotel', err);
+ return err;
+ })
+ ).subscribe(() => {
+ console.log('Hotel deleted');
+ });
+
+ this.router.navigate(['/']);
+ }
+
+ get tags() {
+ return this.form.get('tags') as FormArray;
+ }
+
+ public addTag() {
+ this.tags.push(new FormControl(''));
+ }
+
+ public submit() {
+ if (this.form.valid && !this.errorMessage) {
+ const hotelData: Hotel = {
+ id: this.hotel?.id || 0,
+ hotelName: this.form.value.name,
+ description: this.form.value.description,
+ price: this.form.value.price,
+ rating: this.form.value.rating,
+ imageUrl: this.hotel?.imageUrl || 'https://placehold.co/2000x1050/EEE/31343C',
+ tags: this.form.value.tags || []
+ };
+
+ if (this.hotel) {
+ this.http.put(`api/hotels/${this.hotel.id}`, hotelData).pipe(
+ catchError(err => {
+ console.error('Error updating hotel', err);
+ return err;
+ })
+ ).subscribe(() => {
+ console.log('Hotel updated');
+ });
+ }
+
+ else {
+ this.http.post('api/hotels', hotelData).pipe(
+ catchError(err => {
+ console.error('Error creating hotel', err);
+ return err;
+ })
+ ).subscribe(() => {
+ console.log('Hotel created');
+ });
+ }
+
+ this.form.reset();
+ this.router.navigate(['/']);
+ }
+ }
+
+ private validationErrors: Record = {
+ 'required': 'This field is required',
+ 'minlength': 'This field must be at least 3 characters long',
+ 'min': 'This field must be at least 0',
+ 'max': 'This field must be at most 5',
+ 'name.required': 'Hotel name is required',
+ 'name.minlength': 'Hotel name must be at least 3 characters long',
+ 'description.required': 'Hotel description is required',
+ 'description.minlength': 'Hotel description must be at least 3 characters long',
+ 'price.required': 'Hotel price is required',
+ 'price.min': 'Hotel price cannot be negative',
+ 'rating.required': 'Hotel rating is required',
+ 'rating.min': 'Hotel rating must be at least 0',
+ 'rating.max': 'Hotel rating cannot be more than 5',
+ 'tags.required': 'At least one tag is required',
+ };
+
+ private setError(control: AbstractControl, controlName: string) {
+ if ((control.touched || control.dirty) && control.errors) {
+ const errors = Object.keys(control.errors).map(key => {
+ const errorKey = `${controlName}.${key}`;
+ return this.validationErrors[errorKey] || this.validationErrors[key];
+ });
+ this.errorMessage = errors.join(' ');
+ } else {
+ this.errorMessage = '';
+ }
+ }
+}
+
diff --git a/src/app/HotelItem/HotelItem.component.html b/src/app/hotel-item/HotelItem.component.html
similarity index 100%
rename from src/app/HotelItem/HotelItem.component.html
rename to src/app/hotel-item/HotelItem.component.html
diff --git a/src/app/HotelItem/HotelItem.component.ts b/src/app/hotel-item/HotelItem.component.ts
similarity index 71%
rename from src/app/HotelItem/HotelItem.component.ts
rename to src/app/hotel-item/HotelItem.component.ts
index 0e5d136..c34a644 100644
--- a/src/app/HotelItem/HotelItem.component.ts
+++ b/src/app/hotel-item/HotelItem.component.ts
@@ -1,17 +1,14 @@
-import { Component, Injectable, Input } from "@angular/core";
-import { ChildComponent } from "../Child/child.component";
+import { Component, Input } from "@angular/core";
import { Hotel } from "./hotel";
-import { CurrencyPipe, NgIf } from "@angular/common";
+import {CurrencyPipe, NgForOf, NgIf} from "@angular/common";
import { FormsModule } from "@angular/forms";
-import { StarRatingComponent } from "../star-rating/star-rating.component";
-import { HttpClient } from "@angular/common/http";
import { RouterLink } from "@angular/router";
@Component({
selector: 'app-hotel-item',
standalone: true,
templateUrl: './HotelItem.component.html',
- imports: [ChildComponent, CurrencyPipe, FormsModule, StarRatingComponent, NgIf, RouterLink],
+ imports: [CurrencyPipe, FormsModule, NgIf, RouterLink, NgForOf],
})
export class HotelItem {
diff --git a/src/app/HotelItem/hotel.ts b/src/app/hotel-item/hotel.ts
similarity index 100%
rename from src/app/HotelItem/hotel.ts
rename to src/app/hotel-item/hotel.ts
diff --git a/src/app/hotel-list/hotel-list.component.html b/src/app/hotel-list/hotel-list.component.html
index b4dd192..5aab0f6 100644
--- a/src/app/hotel-list/hotel-list.component.html
+++ b/src/app/hotel-list/hotel-list.component.html
@@ -1,9 +1,28 @@
-
{{'hello' | uppercase | text}}
-
- @if (hotels[0].hotelName) {
+
+
+
{{'hello' | uppercase | text}}
+
+
+
+
+
+
+
diff --git a/src/app/hotel-list/hotel-list.component.ts b/src/app/hotel-list/hotel-list.component.ts
index 528c8ce..0711bfd 100644
--- a/src/app/hotel-list/hotel-list.component.ts
+++ b/src/app/hotel-list/hotel-list.component.ts
@@ -1,22 +1,18 @@
-import { CommonModule, NgFor, NgIf, UpperCasePipe } from '@angular/common';
+import { NgFor, NgIf, UpperCasePipe } from '@angular/common';
import { Component, inject } from '@angular/core';
import { TextPipe } from '../../text.pipe';
-import { SearchComponent } from '../Search/search.component';
-import { HotelService } from '../Parent/services/hotel.service';
-import { Hotel } from '../HotelItem/hotel';
+import { SearchComponent } from '../search/search.component';
+import { HotelService } from '../service/hotel.service';
+import { Hotel } from '../hotel-item/hotel';
import { HttpClient } from '@angular/common/http';
-import { filter, from, last, map, Observable, scan } from 'rxjs';
-import { HotelItem } from '../HotelItem/HotelItem.component';
-
-interface User {
- name: string;
- age: number;
-}
+import { HotelItem } from '../hotel-item/HotelItem.component';
+import { RouterLink } from '@angular/router';
+import { MatButtonModule } from '@angular/material/button';
@Component({
selector: 'app-hotel-list',
standalone: true,
- imports: [UpperCasePipe, TextPipe, SearchComponent, HotelItem, NgFor, NgIf],
+ imports: [UpperCasePipe, TextPipe, SearchComponent, HotelItem, NgFor, NgIf, RouterLink, MatButtonModule],
templateUrl: './hotel-list.component.html',
styleUrl: './hotel-list.component.css'
})
@@ -33,41 +29,5 @@ export class HotelListComponent {
this.http.get>("api/hotels").subscribe(res => {
this.hotels = res;
});
-
- const users = [
- { name: "Max", age: 21 },
- { name: "Peter", age: 31 },
- { name: "Hans", age: 13 },
- { name: "Klaus", age: 51 },
- { name: "Dieter", age: 1 },
- { name: "Jan-Marlon", age: 3 },
- ]
-
- const stream: Observable = from(users);
-
- stream.pipe(
- filter((user) => user.age > 18),
- scan((acc, user) => acc + user.age, 0),
- map((ageSum, index) => ageSum / (index + 1)),
- last(),
- ).subscribe(console.log);
-
- // const stream: Observable = from([5, 1, 2, 12, 5, 14, 17, 5, "testing"]);
-
- // stream.pipe(
- // filter((value) => typeof value === "number"),
- // tap((value) => console.log("Zahl:" + value)),
- // filter((value: number) => value % 2 === 0),
- // tap((value) => console.log("Gerade Zahl: " + value)),
- // toArray(),
- //).subscribe(console.log);
}
-
- public test() {
- console.log(this.search);
- }
-
- // public foundHotels = this.hotels.pipe(
- // filter((hotel) => hotel.hotelName.includes(this.search)),
- // );
}
diff --git a/src/app/not-found/not-found.component.css b/src/app/not-found/not-found.component.css
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/not-found/not-found.component.html b/src/app/not-found/not-found.component.html
new file mode 100644
index 0000000..62c460a
--- /dev/null
+++ b/src/app/not-found/not-found.component.html
@@ -0,0 +1,7 @@
+
diff --git a/src/app/not-found/not-found.component.spec.ts b/src/app/not-found/not-found.component.spec.ts
new file mode 100644
index 0000000..5b65d9e
--- /dev/null
+++ b/src/app/not-found/not-found.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { NotFoundComponent } from './not-found.component';
+
+describe('NotFoundComponent', () => {
+ let component: NotFoundComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [NotFoundComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(NotFoundComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/not-found/not-found.component.ts b/src/app/not-found/not-found.component.ts
new file mode 100644
index 0000000..10c6221
--- /dev/null
+++ b/src/app/not-found/not-found.component.ts
@@ -0,0 +1,17 @@
+import { Component } from '@angular/core';
+import { MatButtonModule } from '@angular/material/button';
+import { RouterLink } from '@angular/router';
+
+@Component({
+ selector: 'app-not-found',
+ standalone: true,
+ imports: [
+ MatButtonModule,
+ RouterLink
+ ],
+ templateUrl: './not-found.component.html',
+ styleUrl: './not-found.component.css'
+})
+export class NotFoundComponent {
+
+}
diff --git a/src/app/service/hotel.service.ts b/src/app/service/hotel.service.ts
new file mode 100644
index 0000000..31a5f02
--- /dev/null
+++ b/src/app/service/hotel.service.ts
@@ -0,0 +1,51 @@
+import { Injectable } from "@angular/core";
+import { Hotel } from "../hotel-item/hotel";
+import { HttpClient, HttpErrorResponse } from "@angular/common/http";
+import { Observable, throwError, catchError } from "rxjs";
+
+@Injectable({
+ providedIn: 'root'
+})
+export class HotelService {
+ private apiUrl = 'api/hotels';
+
+ constructor(private http: HttpClient) {}
+
+ private handleError(error: HttpErrorResponse) {
+ let errorMessage = 'An error occurred';
+ if (error.error instanceof ErrorEvent) {
+ // Client-side error
+ errorMessage = `Error: ${error.error.message}`;
+ } else {
+ // Server-side error
+ errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`;
+ }
+ console.error(errorMessage);
+ return throwError(() => errorMessage);
+ }
+
+ public getHotels(): Observable {
+ return this.http.get(this.apiUrl)
+ .pipe(catchError(this.handleError));
+ }
+
+ public getHotel(id: number): Observable {
+ return this.http.get(`${this.apiUrl}/${id}`)
+ .pipe(catchError(this.handleError));
+ }
+
+ public createHotel(hotel: Hotel): Observable {
+ return this.http.post(this.apiUrl, hotel)
+ .pipe(catchError(this.handleError));
+ }
+
+ public updateHotel(hotel: Hotel): Observable {
+ return this.http.put(`${this.apiUrl}/${hotel.id}`, hotel)
+ .pipe(catchError(this.handleError));
+ }
+
+ public deleteHotel(id: number): Observable<{}> {
+ return this.http.delete(`${this.apiUrl}/${id}`)
+ .pipe(catchError(this.handleError));
+ }
+}
diff --git a/src/index.html b/src/index.html
index f3614f8..0ca266f 100644
--- a/src/index.html
+++ b/src/index.html
@@ -7,8 +7,10 @@
+
+
-
+
diff --git a/src/styles.css b/src/styles.css
index 2adbbf3..3ccb4fd 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -6,3 +6,6 @@
.button {
@apply border-black rounded px-3 py-1 border-[3px];
}
+
+html, body { height: 100%; }
+body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }
\ No newline at end of file
diff --git a/tailwind.config.js b/tailwind.config.js
index 068e770..d5856ac 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -1,18 +1,9 @@
/** @type {import('tailwindcss').Config} */
-const colors = require('tailwindcss/colors');
-
module.exports = {
content: [
"./src/**/*.{html,ts}",
],
- theme: {
- extend: {
- colors: {
- ...colors,
- }
- },
- },
plugins: [],
}