This commit is contained in:
Constantin Simonis 2025-02-04 09:15:20 +01:00
parent 59e90dc534
commit 9ab7a128f3
Signed by: csimonis
GPG Key ID: 758DD9C506603183
19 changed files with 383 additions and 286 deletions

View File

@ -1,5 +1,4 @@
import {ErrorHandler as NgErrorHandler} from "@angular/core";
import { ErrorHandler as NgErrorHandler } from '@angular/core';
export class ErrorHandler implements NgErrorHandler {
handleError(error: any): void {

View File

@ -1 +0,0 @@

View File

@ -24,6 +24,8 @@ describe('AppComponent', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('h1')?.textContent).toContain('Hello, hotel-manager');
expect(compiled.querySelector('h1')?.textContent).toContain(
'Hello, hotel-manager',
);
});
});

View File

@ -1,16 +1,12 @@
import {Component, inject, OnInit} from '@angular/core';
import {filter, from, map, reduce} from "rxjs";
import {Router, RouterOutlet} from "@angular/router";
import { Component, inject, OnInit } from '@angular/core';
import { filter, from, map, reduce } from 'rxjs';
import { Router, RouterOutlet } from '@angular/router';
@Component({
selector: 'app-root',
standalone: true,
imports: [
RouterOutlet
],
template: `
<router-outlet />
`
imports: [RouterOutlet],
template: ` <router-outlet /> `,
})
export class AppComponent implements OnInit {
router: Router = inject(Router);
@ -58,17 +54,24 @@ export class AppComponent implements OnInit {
{
name: 'Victor',
age: 39,
}
},
];
from(users).pipe(
filter(user => user.age >= 18),
reduce((acc, user) => {
acc.age += user.age
acc.count++;
return acc;
}, {age: 0, count: 0}),
map(data => data.age / data.count),
).subscribe((data) => {console.log("avg age: ", data)});
from(users)
.pipe(
filter((user) => user.age >= 18),
reduce(
(acc, user) => {
acc.age += user.age;
acc.count++;
return acc;
},
{ age: 0, count: 0 },
),
map((data) => data.age / data.count),
)
.subscribe((data) => {
console.log('avg age: ', data);
});
}
}

View File

@ -1,19 +1,23 @@
import {ApplicationConfig, importProvidersFrom, provideZoneChangeDetection} from '@angular/core';
import {
ApplicationConfig,
importProvidersFrom,
provideZoneChangeDetection,
} from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
import {registerLocaleData} from "@angular/common";
import localeDe from "@angular/common/locales/de"
import localeCn from "@angular/common/locales/en"
import localeJap from "@angular/common/locales/en"
import {provideHttpClient} from "@angular/common/http";
import {InMemoryWebApiModule} from "angular-in-memory-web-api";
import {HotelDataService} from "./hotel/service/HotelData.service";
import {ErrorHandler} from "./ErrorHandler";
import {ErrorHandler as NgErrorHandler} from "@angular/core";
import { registerLocaleData } from '@angular/common';
import localeDe from '@angular/common/locales/de';
import localeCn from '@angular/common/locales/en';
import localeJap from '@angular/common/locales/en';
import { provideHttpClient } from '@angular/common/http';
import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
import { HotelDataService } from './hotel/service/HotelData.service';
import { ErrorHandler } from './ErrorHandler';
import { ErrorHandler as NgErrorHandler } from '@angular/core';
registerLocaleData(localeDe, 'de-DE')
registerLocaleData(localeCn, 'cn-CN')
registerLocaleData(localeJap, 'ja-JP')
registerLocaleData(localeDe, 'de-DE');
registerLocaleData(localeCn, 'cn-CN');
registerLocaleData(localeJap, 'ja-JP');
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
@ -22,7 +26,7 @@ export const appConfig: ApplicationConfig = {
importProvidersFrom(InMemoryWebApiModule.forRoot(HotelDataService)),
{
provide: NgErrorHandler,
useClass: ErrorHandler
}
]
useClass: ErrorHandler,
},
],
};

View File

@ -1,7 +1,7 @@
import {Routes} from '@angular/router';
import {HotelsComponent} from "./hotel/component/hotels.component";
import {HotelComponent} from "./hotel/component/hotel.component";
import {CreateHotelComponent} from "./hotel/component/create-hotel.component";
import { Routes } from '@angular/router';
import { HotelsComponent } from './hotel/component/hotels.component';
import { HotelComponent } from './hotel/component/hotel.component';
import { CreateHotelComponent } from './hotel/component/create-hotel.component';
export const routes: Routes = [
{
@ -12,11 +12,11 @@ export const routes: Routes = [
{
path: 'hotels/new',
component: CreateHotelComponent,
title: 'New Hotel'
title: 'New Hotel',
},
{
path: 'hotels/:hotelId',
component: HotelComponent,
title: 'Hotel',
}
},
];

View File

@ -1,25 +1,20 @@
import {Component, EventEmitter, Output} from "@angular/core";
import {FormsModule, ReactiveFormsModule} from "@angular/forms";
import {Lang} from "./lang";
import { Component, EventEmitter, Output } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { Lang } from './lang';
@Component({
standalone: true,
selector: "app-currency",
imports: [
ReactiveFormsModule,
FormsModule
],
selector: 'app-currency',
imports: [ReactiveFormsModule, FormsModule],
template: `
<select [ngModel]="currency" (ngModelChange)="setCurrency($event)">
@for (currency of currencies; track currency.code) {
<option value="{{currency.name}}">{{ currency.name }}</option>
<option value="{{ currency.name }}">{{ currency.name }}</option>
}
</select>
`
`,
})
export class CurrencyComponent {
@Output()
public currency: EventEmitter<Lang> = new EventEmitter();
@ -27,24 +22,24 @@ export class CurrencyComponent {
{
name: 'de',
code: 'de-DE',
currency: 'EUR'
currency: 'EUR',
},
{
name: 'en',
code: 'en-US',
currency: 'USD'
currency: 'USD',
},
{
name: 'jap',
code: 'ja-JP',
currency: 'JPY'
currency: 'JPY',
},
{
name: 'cn',
code: 'cn-CN',
currency: 'CNY'
}
]
currency: 'CNY',
},
];
public setCurrency(currencyInput: string): void {
for (const currency of this.currencies) {

View File

@ -1,22 +1,18 @@
import {Component, inject} from "@angular/core";
import {Hotel} from "../model/hotel";
import {HotelService} from "../service/hotel.service";
import {EditHotelComponent} from "./edit-hotel.component";
import {Router} from "@angular/router";
import { Component, inject } from '@angular/core';
import { Hotel } from '../model/hotel';
import { HotelService } from '../service/hotel.service';
import { EditHotelComponent } from './edit-hotel.component';
import { Router } from '@angular/router';
@Component({
selector: "app-create-hotel",
selector: 'app-create-hotel',
standalone: true,
imports: [
EditHotelComponent
],
imports: [EditHotelComponent],
template: `
<app-edit-hotel (updateHotel)="create($event)"></app-edit-hotel>
`
`,
})
export class CreateHotelComponent {
hotelService: HotelService = inject(HotelService);
router: Router = inject(Router);

View File

@ -1,14 +1,11 @@
import {Component, inject, Input} from "@angular/core";
import {HotelService} from "../service/hotel.service";
import {Router} from "@angular/router";
import { Component, inject, Input } from '@angular/core';
import { HotelService } from '../service/hotel.service';
import { Router } from '@angular/router';
@Component({
selector: 'app-delete-hotel',
standalone: true,
template: `
<button (click)="delete()">Delete Hotel</button>
`
template: ` <button (click)="delete()">Delete Hotel</button> `,
})
export class DeleteHotelComponent {
@Input()
@ -20,7 +17,7 @@ export class DeleteHotelComponent {
delete() {
if (confirm('Are you sure?')) {
this.hotelService.deleteById(this.id).subscribe()
this.hotelService.deleteById(this.id).subscribe();
this.router.navigate(['/hotels']);
}
}

View File

@ -1,4 +1,11 @@
import {ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
import {
ChangeDetectionStrategy,
Component,
EventEmitter,
Input,
OnInit,
Output,
} from '@angular/core';
import {
AbstractControl,
FormArray,
@ -7,13 +14,13 @@ import {
FormsModule,
ReactiveFormsModule,
ValidationErrors,
Validators
} from "@angular/forms";
import {Hotel} from "../model/hotel";
import {RouterLink} from "@angular/router";
import {NgForOf, NgIf} from "@angular/common";
import {debounceTime} from "rxjs";
import {ErrorMsgComponent} from "./error-msg.component";
Validators,
} from '@angular/forms';
import { Hotel } from '../model/hotel';
import { RouterLink } from '@angular/router';
import { NgForOf, NgIf } from '@angular/common';
import { debounceTime } from 'rxjs';
import { ErrorMsgComponent } from './error-msg.component';
@Component({
selector: 'app-edit-hotel',
@ -29,83 +36,101 @@ import {ErrorMsgComponent} from "./error-msg.component";
template: `
<ng-form [formGroup]="form" (ngSubmit)="submit()">
<label for="name">Name</label>
<br>
<input id="name" type="text" formControlName="name">
<app-error-msg [msg]="errorMsgs['name']"/>
<br>
<br />
<input id="name" type="text" formControlName="name" />
<app-error-msg [msg]="errorMsgs['name']" />
<br />
<label for="description">Description</label>
<br>
<input id="description" type="text" formControlName="description">
<app-error-msg [msg]="errorMsgs['description']"/>
<br>
<br />
<input id="description" type="text" formControlName="description" />
<app-error-msg [msg]="errorMsgs['description']" />
<br />
<label for="price">Price</label>
<br>
<input id="price" type="number" formControlName="price">
<app-error-msg [msg]="errorMsgs['price']"/>
<br>
<br />
<input id="price" type="number" formControlName="price" />
<app-error-msg [msg]="errorMsgs['price']" />
<br />
<label for="rating">Rating</label>
<br>
<input id="rating" type="number" formControlName="rating">
<app-error-msg [msg]="errorMsgs['rating']"/>
<br>
<br>
<br />
<input id="rating" type="number" formControlName="rating" />
<app-error-msg [msg]="errorMsgs['rating']" />
<br />
<br />
<label for="email">Email</label>
<input id="email" type="checkbox" name="contactType" value="Email" [checked]="activeContact == 'email'"
(click)="changeContactType('email')">
<br>
<input
id="email"
type="checkbox"
name="contactType"
value="Email"
[checked]="activeContact == 'email'"
(click)="changeContactType('email')"
/>
<br />
<label for="phoneNumber">Phone Number</label>
<input id="phoneNumber" type="checkbox" name="contactType" value="PhoneNumber"
[checked]="activeContact == 'phoneNumber'" (click)="changeContactType('phoneNumber')">
<br>
<input
id="phoneNumber"
type="checkbox"
name="contactType"
value="PhoneNumber"
[checked]="activeContact == 'phoneNumber'"
(click)="changeContactType('phoneNumber')"
/>
<br />
<label for="none">None</label>
<input id="none" type="checkbox" name="contactType" value="none" [checked]="activeContact == 'none'"
(click)="changeContactType('none')">
<input
id="none"
type="checkbox"
name="contactType"
value="none"
[checked]="activeContact == 'none'"
(click)="changeContactType('none')"
/>
<br>
<br>
<br />
<br />
@if (activeContact === 'email') {
<app-error-msg [msg]="errorMsgs['emailGroup']"/>
<app-error-msg [msg]="errorMsgs['emailGroup']" />
<div formGroupName="emailGroup">
<app-error-msg [msg]="errorMsgs['email']"/>
<br>
<app-error-msg [msg]="errorMsgs['email']" />
<br />
<label for="email">Email</label>
<br>
<input type="email" formControlName="email" required>
<br>
<br />
<input type="email" formControlName="email" required />
<br />
<label for="emailConfirm">Confirm Email</label>
<app-error-msg [msg]="errorMsgs['emailConfirm']"/>
<br>
<input type="email" formControlName="emailConfirm" required>
<app-error-msg [msg]="errorMsgs['emailConfirm']" />
<br />
<input type="email" formControlName="emailConfirm" required />
</div>
} @else if (activeContact === 'phoneNumber') {
<app-error-msg [msg]="errorMsgs['phoneNumberGroup']"/>
<app-error-msg [msg]="errorMsgs['phoneNumberGroup']" />
<div formGroupName="phoneNumberGroup">
<app-error-msg [msg]="errorMsgs['phoneNumber']"/>
<br>
<app-error-msg [msg]="errorMsgs['phoneNumber']" />
<br />
<label for="phoneNumber">Phone Number</label>
<br>
<input type="number" formControlName="phoneNumber" required>
<br>
<br />
<input type="number" formControlName="phoneNumber" required />
<br />
<label for="phoneNumberConfirm">Confirm Phone Number</label>
<app-error-msg [msg]="errorMsgs['phoneNumberConfirm']"/>
<br>
<input type="number" formControlName="phoneNumberConfirm" required>
<app-error-msg [msg]="errorMsgs['phoneNumberConfirm']" />
<br />
<input type="number" formControlName="phoneNumberConfirm" required />
</div>
}
<br>
<br>
<br />
<br />
<button (click)="addTag()">+</button>
<div formArrayName="tags">
<div *ngFor="let tag of getTags().controls; let i = index">
<input type="text" placeholder="tag" [formControlName]="i">
<input type="text" placeholder="tag" [formControlName]="i" />
<button (click)="deleteTag(tag)">-</button>
</div>
<app-error-msg [msg]="errorMsgs['tags']"/>
<app-error-msg [msg]="errorMsgs['tags']" />
</div>
<br>
<br>
<br />
<br />
<button type="submit" (click)="submit()">Submit</button>
</ng-form>
@ -119,9 +144,9 @@ export class EditHotelComponent implements OnInit {
hotel: Hotel | null = null;
@Output()
updateHotel: EventEmitter<Hotel> = new EventEmitter
updateHotel: EventEmitter<Hotel> = new EventEmitter();
form!: FormGroup
form!: FormGroup;
validationErrors: Record<string, string> = {
required: 'This field is required',
@ -154,13 +179,19 @@ export class EditHotelComponent implements OnInit {
this.form = new FormGroup({
name: new FormControl(this.hotel?.hotelName, [Validators.required]),
description: new FormControl(this.hotel?.description, [Validators.required]),
description: new FormControl(this.hotel?.description, [
Validators.required,
]),
price: new FormControl(this.hotel?.price, [Validators.required]),
rating: new FormControl(this.hotel?.rating, [Validators.required, Validators.min(0), Validators.max(5)]),
rating: new FormControl(this.hotel?.rating, [
Validators.required,
Validators.min(0),
Validators.max(5),
]),
tags: new FormArray(tags),
});
this.initGroups()
this.initGroups();
if (this.hotel?.email) {
this.form.addControl('emailGroup', this.emailGroup);
@ -168,7 +199,7 @@ export class EditHotelComponent implements OnInit {
this.form.addControl('phoneNumberGroup', this.phoneNumberGroup);
}
Object.keys(this.form.controls).forEach(controlName => {
Object.keys(this.form.controls).forEach((controlName) => {
let debounce = 1000;
const control = this.form.get(controlName);
if (!control) {
@ -180,7 +211,7 @@ export class EditHotelComponent implements OnInit {
}
control?.valueChanges?.pipe(debounceTime(debounce)).subscribe(() => {
this.setErrorMessage(controlName, control)
this.setErrorMessage(controlName, control);
});
});
}
@ -192,7 +223,7 @@ export class EditHotelComponent implements OnInit {
}
const hotel: Hotel = {
imageUrl: this.hotel?.imageUrl ?? "",
imageUrl: this.hotel?.imageUrl ?? '',
hotelName: this.form.value.name,
description: this.form.value.description,
price: this.form.value.price,
@ -200,10 +231,10 @@ export class EditHotelComponent implements OnInit {
id: this.hotel?.id ?? 0,
tags: this.form.value.tags ?? [],
email: this.form.value.email,
phoneNumber: this.form.value.phoneNumber
phoneNumber: this.form.value.phoneNumber,
};
this.updateHotel.emit(hotel)
this.updateHotel.emit(hotel);
this.hotel = hotel;
}
@ -221,11 +252,13 @@ export class EditHotelComponent implements OnInit {
}
setErrorMessage(controlName: string, control: AbstractControl) {
this.errorMsgs[controlName] = Object.keys(control.errors ?? {}).map((key) => this.validationErrors[key]).join(' ');
this.errorMsgs[controlName] = Object.keys(control.errors ?? {})
.map((key) => this.validationErrors[key])
.join(' ');
}
showErrors() {
Object.keys(this.form.controls).forEach(controlName => {
Object.keys(this.form.controls).forEach((controlName) => {
const control = this.form.get(controlName);
if (!control) {
return;
@ -254,7 +287,7 @@ export class EditHotelComponent implements OnInit {
}
if (email.value !== emailConfirm.value) {
return {emailMatch: true};
return { emailMatch: true };
}
return null;
@ -269,21 +302,41 @@ export class EditHotelComponent implements OnInit {
}
if (phoneNumber.value !== phoneNumberConfirm.value) {
return {phoneNumberMatch: true};
return { phoneNumberMatch: true };
}
return null;
}
private initGroups() {
this.emailGroup = new FormGroup({
email: new FormControl(this.hotel?.email, [Validators.required, Validators.email]),
emailConfirm: new FormControl(this.hotel?.email, [Validators.required, Validators.email]),
}, {validators: this.emailMatch});
this.emailGroup = new FormGroup(
{
email: new FormControl(this.hotel?.email, [
Validators.required,
Validators.email,
]),
emailConfirm: new FormControl(this.hotel?.email, [
Validators.required,
Validators.email,
]),
},
{ validators: this.emailMatch },
);
this.phoneNumberGroup = new FormGroup({
phoneNumber: new FormControl(this.hotel?.phoneNumber, [Validators.required, Validators.maxLength(10), Validators.minLength(10)]),
phoneNumberConfirm: new FormControl(this.hotel?.phoneNumber, [Validators.required, Validators.maxLength(10), Validators.minLength(10)]),
}, {validators: this.phoneNumberMatch});
this.phoneNumberGroup = new FormGroup(
{
phoneNumber: new FormControl(this.hotel?.phoneNumber, [
Validators.required,
Validators.maxLength(10),
Validators.minLength(10),
]),
phoneNumberConfirm: new FormControl(this.hotel?.phoneNumber, [
Validators.required,
Validators.maxLength(10),
Validators.minLength(10),
]),
},
{ validators: this.phoneNumberMatch },
);
}
}

View File

@ -1,13 +1,10 @@
import {Component, Input} from "@angular/core";
import {NgIf} from "@angular/common";
import { Component, Input } from '@angular/core';
import { NgIf } from '@angular/common';
@Component({
standalone: true,
selector: 'app-error-msg',
imports: [
NgIf
],
imports: [NgIf],
template: `
<div *ngIf="msg" class="alert alert-danger border">
{{ msg }}
@ -38,7 +35,7 @@ import {NgIf} from "@angular/common";
transform: rotate(180deg);
}
}
`
`,
})
export class ErrorMsgComponent {
@Input()

View File

@ -1,13 +1,12 @@
import {Component, inject, OnInit} from "@angular/core";
import {Hotel} from "../model/hotel"
import {CurrencyPipe, NgOptimizedImage} from "@angular/common";
import {StarComponent} from "./star.component";
import {ActivatedRoute} from "@angular/router";
import {HotelService} from "../service/hotel.service";
import {catchError, EMPTY} from "rxjs";
import {EditHotelComponent} from "./edit-hotel.component";
import {DeleteHotelComponent} from "./delete-hotel.component";
import { Component, inject, OnInit } from '@angular/core';
import { Hotel } from '../model/hotel';
import { CurrencyPipe, NgOptimizedImage } from '@angular/common';
import { StarComponent } from './star.component';
import { ActivatedRoute } from '@angular/router';
import { HotelService } from '../service/hotel.service';
import { catchError, EMPTY } from 'rxjs';
import { EditHotelComponent } from './edit-hotel.component';
import { DeleteHotelComponent } from './delete-hotel.component';
@Component({
standalone: true,
@ -17,26 +16,25 @@ import {DeleteHotelComponent} from "./delete-hotel.component";
StarComponent,
NgOptimizedImage,
EditHotelComponent,
DeleteHotelComponent
DeleteHotelComponent,
],
template: `
<div style="border: white 2px; border-radius: 2px">
@if (hotel && !alert) {
<app-edit-hotel [hotel]="hotel" (updateHotel)="update($event)" />
<app-delete-hotel [id]="hotel.id" />
} @else if(alert) {
<h2>{{alert}}</h2>
} @else if (alert) {
<h2>{{ alert }}</h2>
} @else {
<h2>loading</h2>
}
</div>
`
`,
})
export class HotelComponent implements OnInit {
protected hotel!: Hotel;
protected alert: string|undefined
protected alert: string | undefined;
private route: ActivatedRoute = inject(ActivatedRoute);
@ -44,15 +42,22 @@ export class HotelComponent implements OnInit {
ngOnInit(): void {
const hotelId = this.route.snapshot.params['hotelId'];
this.hotelService.getHotelById(hotelId)
.pipe(catchError((err) => {
this.alert = `Hotel could not be retrieved: ${err.message}`
return EMPTY;
}))
.subscribe({next: (hotel: Hotel) => {this.hotel = hotel}})
this.hotelService
.getHotelById(hotelId)
.pipe(
catchError((err) => {
this.alert = `Hotel could not be retrieved: ${err.message}`;
return EMPTY;
}),
)
.subscribe({
next: (hotel: Hotel) => {
this.hotel = hotel;
},
});
}
update(hotel: Hotel): void {
this.hotelService.updateHotelById(hotel).subscribe()
this.hotelService.updateHotelById(hotel).subscribe();
}
}

View File

@ -1,52 +1,67 @@
import {ChangeDetectionStrategy, Component, inject} from "@angular/core";
import {HotelComponent} from "./hotel.component";
import {Hotel} from "../model/hotel";
import {FormsModule} from "@angular/forms";
import {Lang} from "../../currency/lang";
import {HotelService} from "../service/hotel.service";
import {Observable} from "rxjs";
import {AsyncPipe, NgIf} from "@angular/common";
import {CurrencyComponent} from "../../currency/currency.component";
import {RouterLink} from "@angular/router";
import {StarComponent} from "./star.component";
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { HotelComponent } from './hotel.component';
import { Hotel } from '../model/hotel';
import { FormsModule } from '@angular/forms';
import { Lang } from '../../currency/lang';
import { HotelService } from '../service/hotel.service';
import { Observable } from 'rxjs';
import { AsyncPipe, NgIf } from '@angular/common';
import { CurrencyComponent } from '../../currency/currency.component';
import { RouterLink } from '@angular/router';
import { StarComponent } from './star.component';
@Component({
standalone: true,
template: `
<app-currency (currency)="currency = $event"></app-currency>
<form>
<input name="search" [ngModel]="search" (ngModelChange)="searchEvent($event)">
<input
name="search"
[ngModel]="search"
(ngModelChange)="searchEvent($event)"
/>
</form>
<p routerLink="/hotels/new">Create Hotel</p>
@for (hotel of (matchingHotels | async); track hotel.id) {
@for (hotel of matchingHotels | async; track hotel.id) {
<div *ngIf="hotel.hotelName.toLowerCase().includes(search)">
<div>{{ hotel.hotelName }}</div>
<img src="{{hotel.imageUrl}}" alt="{{hotel.hotelName}}" height="64" width="64">
<br>
<button routerLink="{{hotel.id}}">Details</button>
<hr>
</div>
<img
src="{{ hotel.imageUrl }}"
alt="{{ hotel.hotelName }}"
height="64"
width="64"
/>
<br />
<button routerLink="{{ hotel.id }}">Details</button>
<hr />
</div>
} @empty {
<h1>no matching results for {{ search }}</h1>
}
`,
imports: [FormsModule, HotelComponent, AsyncPipe, CurrencyComponent, RouterLink, StarComponent, NgIf],
imports: [
FormsModule,
HotelComponent,
AsyncPipe,
CurrencyComponent,
RouterLink,
StarComponent,
NgIf,
],
providers: [HotelService],
selector: 'app-hotels',
changeDetection: ChangeDetectionStrategy.OnPush
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class HotelsComponent {
public currency: Lang = {name: 'de', code: 'de-DE', currency: 'EUR'};
public currency: Lang = { name: 'de', code: 'de-DE', currency: 'EUR' };
public search: string = '';
private hotelService: HotelService= inject(HotelService);
private hotelService: HotelService = inject(HotelService);
public matchingHotels: Observable<Hotel[]> = this.hotelService.getHotels();
public searchEvent(input: string) {
this.search = input.toLowerCase();
}
}

View File

@ -1,4 +1,4 @@
import {Component, Input} from '@angular/core';
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-star',
@ -6,10 +6,32 @@ import {Component, Input} from '@angular/core';
imports: [],
template: `
@for (_ of getList(); track null) {
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" color="yellow"><path fill="currentColor" d="m5.825 21l1.625-7.025L2 9.25l7.2-.625L12 2l2.8 6.625l7.2.625l-5.45 4.725L18.175 21L12 17.275z"/></svg>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
color="yellow"
>
<path
fill="currentColor"
d="m5.825 21l1.625-7.025L2 9.25l7.2-.625L12 2l2.8 6.625l7.2.625l-5.45 4.725L18.175 21L12 17.275z"
/>
</svg>
}
@if (rating % 1 >= 0.5) {
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" color="yellow"><path fill="currentColor" d="M12 2L9.19 8.62L2 9.24l5.45 4.73L5.82 21L12 17.27z"/></svg>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
color="yellow"
>
<path
fill="currentColor"
d="M12 2L9.19 8.62L2 9.24l5.45 4.73L5.82 21L12 17.27z"
/>
</svg>
}
`,
})

View File

@ -2,8 +2,8 @@ export interface Hotel {
id: number;
hotelName: string;
description: string;
email: string|null,
phoneNumber: string|null,
email: string | null;
phoneNumber: string | null;
price: number;
imageUrl: string;
rating: number;

View File

@ -1,57 +1,55 @@
import {InMemoryDbService} from "angular-in-memory-web-api";
import {Hotel} from "../model/hotel";
import { InMemoryDbService } from 'angular-in-memory-web-api';
import { Hotel } from '../model/hotel';
export class HotelDataService implements InMemoryDbService{
export class HotelDataService implements InMemoryDbService {
createDb(): Record<string, Hotel[]> {
const hotels: Hotel[] = [
{
"id": 1,
"hotelName": "Buea süßes Leben",
"description": "Schöne Aussicht am Meer",
"price": 230.5,
"imageUrl": "assets/img/heisenberg.jpg",
"rating": 3.5,
"tags": ["Meer", "Berge"],
"email": "buea@mail.com",
"phoneNumber": "1234567890"
id: 1,
hotelName: 'Buea süßes Leben',
description: 'Schöne Aussicht am Meer',
price: 230.5,
imageUrl: 'assets/img/heisenberg.jpg',
rating: 3.5,
tags: ['Meer', 'Berge'],
email: 'buea@mail.com',
phoneNumber: '1234567890',
},
{
"id": 2,
"hotelName": "Marrakesch",
"description": "Genießen Sie den Blick auf die Berge",
"price": 145.5,
"imageUrl": "assets/img/kjan.png",
"rating": 5,
"tags": ["Meer", "Berge"],
"email": "marrakesch@mail.com",
"phoneNumber": "1234567890"
id: 2,
hotelName: 'Marrakesch',
description: 'Genießen Sie den Blick auf die Berge',
price: 145.5,
imageUrl: 'assets/img/kjan.png',
rating: 5,
tags: ['Meer', 'Berge'],
email: 'marrakesch@mail.com',
phoneNumber: '1234567890',
},
{
"id": 3,
"hotelName": "Abuja neuer Palast",
"description": "Kompletter Aufenthalt mit Autoservice",
"price": 120.12,
"imageUrl": "assets/img/huy.png",
"rating": 4,
"tags": ["Meer", "Berge"],
"email": "abuja@mail.com",
"phoneNumber": "1234567890"
id: 3,
hotelName: 'Abuja neuer Palast',
description: 'Kompletter Aufenthalt mit Autoservice',
price: 120.12,
imageUrl: 'assets/img/huy.png',
rating: 4,
tags: ['Meer', 'Berge'],
email: 'abuja@mail.com',
phoneNumber: '1234567890',
},
{
"id": 4,
"hotelName": "OUR Hotel",
"description": "Wunderschönes Ambiente für Ihren Aufenthalt",
"price": 135.12,
"imageUrl": "assets/img/rat.png",
"rating": 2.5,
"tags": ["Meer", "Berge"],
"email": "our@mail.com",
"phoneNumber": "1234567890"
}
id: 4,
hotelName: 'OUR Hotel',
description: 'Wunderschönes Ambiente für Ihren Aufenthalt',
price: 135.12,
imageUrl: 'assets/img/rat.png',
rating: 2.5,
tags: ['Meer', 'Berge'],
email: 'our@mail.com',
phoneNumber: '1234567890',
},
];
return { hotels };
}
}

View File

@ -1,10 +1,10 @@
import {HttpClient} from "@angular/common/http";
import {inject, Injectable} from "@angular/core";
import {Observable, shareReplay} from "rxjs";
import {Hotel} from "../model/hotel";
import { HttpClient } from '@angular/common/http';
import { inject, Injectable } from '@angular/core';
import { Observable, shareReplay } from 'rxjs';
import { Hotel } from '../model/hotel';
@Injectable({
providedIn: "root",
providedIn: 'root',
})
export class HotelService {
private httpClient: HttpClient = inject(HttpClient);
@ -18,11 +18,11 @@ export class HotelService {
}
public updateHotelById(hotel: Hotel): Observable<Object> {
return this.httpClient.put<Hotel>(`/api/hotels/${hotel.id}`, hotel)
return this.httpClient.put<Hotel>(`/api/hotels/${hotel.id}`, hotel);
}
public createHotel(hotel: Hotel): Observable<Hotel> {
return this.httpClient.post<Hotel>(`/api/hotels`, hotel)
return this.httpClient.post<Hotel>(`/api/hotels`, hotel);
}
public deleteById(id: number): Observable<Hotel> {

View File

@ -1,15 +1,26 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>HotelManager</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/js/bootstrap.min.js" integrity="sha512-ykZ1QQr0Jy/4ZkvKuqWn4iF3lqPZyij9iRv6sGqLRdTPkY69YX6+7wvVGmsdBbiIfN/8OdsI7HABjvEok6ZopQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap-grid.min.css" integrity="sha512-i1b/nzkVo97VN5WbEtaPebBG8REvjWeqNclJ6AItj7msdVcaveKrlIIByDpvjk5nwHjXkIqGZscVxOrTb9tsMA==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
<head>
<meta charset="utf-8" />
<title>HotelManager</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script
src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/js/bootstrap.min.js"
integrity="sha512-ykZ1QQr0Jy/4ZkvKuqWn4iF3lqPZyij9iRv6sGqLRdTPkY69YX6+7wvVGmsdBbiIfN/8OdsI7HABjvEok6ZopQ=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
></script>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap-grid.min.css"
integrity="sha512-i1b/nzkVo97VN5WbEtaPebBG8REvjWeqNclJ6AItj7msdVcaveKrlIIByDpvjk5nwHjXkIqGZscVxOrTb9tsMA=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
/>
<link rel="icon" type="image/x-icon" href="favicon.ico" />
</head>
<body>
<app-root></app-root>
</body>
</html>

View File

@ -2,5 +2,6 @@ import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, appConfig)
.catch((err) => console.error(err));
bootstrapApplication(AppComponent, appConfig).catch((err) =>
console.error(err),
);