@@ -4,6 +4,8 @@
|
|
4 |
import { HomeComponent } from './home/home.component';
|
5 |
import { BookListComponent } from './book-list/book-list.component';
|
6 |
import { BookDetailsComponent } from './book-details/book-details.component';
|
|
|
|
|
7 |
|
8 |
export const routes: Routes = [
|
9 |
{
|
@@ -22,6 +24,19 @@
|
|
22 |
{
|
23 |
path: 'books/:isbn',
|
24 |
component: BookDetailsComponent
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
25 |
}
|
26 |
];
|
27 |
|
4 |
import { HomeComponent } from './home/home.component';
|
5 |
import { BookListComponent } from './book-list/book-list.component';
|
6 |
import { BookDetailsComponent } from './book-details/book-details.component';
|
7 |
+
import { CreateBookComponent } from './create-book/create-book.component';
|
8 |
+
import { EditBookComponent } from './edit-book/edit-book.component';
|
9 |
|
10 |
export const routes: Routes = [
|
11 |
{
|
24 |
{
|
25 |
path: 'books/:isbn',
|
26 |
component: BookDetailsComponent
|
27 |
+
},
|
28 |
+
{
|
29 |
+
path: 'admin',
|
30 |
+
redirectTo: 'admin/create',
|
31 |
+
pathMatch: 'full'
|
32 |
+
},
|
33 |
+
{
|
34 |
+
path: 'admin/create',
|
35 |
+
component: CreateBookComponent
|
36 |
+
},
|
37 |
+
{
|
38 |
+
path: 'admin/edit/:isbn',
|
39 |
+
component: EditBookComponent
|
40 |
}
|
41 |
];
|
42 |
|
@@ -1,5 +1,6 @@
|
|
1 |
<div class="ui menu">
|
2 |
<a routerLink="home" routerLinkActive="active" class="item">Home</a>
|
3 |
<a routerLink="books" routerLinkActive="active" class="item">Bücher</a>
|
|
|
4 |
</div>
|
5 |
<router-outlet></router-outlet>
|
1 |
<div class="ui menu">
|
2 |
<a routerLink="home" routerLinkActive="active" class="item">Home</a>
|
3 |
<a routerLink="books" routerLinkActive="active" class="item">Bücher</a>
|
4 |
+
<a routerLink="admin" routerLinkActive="active" class="item">Administration</a>
|
5 |
</div>
|
6 |
<router-outlet></router-outlet>
|
@@ -1,6 +1,8 @@
|
|
1 |
import { CommonModule } from '@angular/common';
|
2 |
import { NgModule } from '@angular/core';
|
3 |
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
|
|
|
|
|
4 |
|
5 |
import { AppRoutingModule } from './app-routing.module.one-app';
|
6 |
import { AppComponent } from './app.component';
|
@@ -10,6 +12,10 @@
|
|
10 |
import { BookDetailsComponent } from './book-details/book-details.component';
|
11 |
import { SearchComponent } from './search/search.component';
|
12 |
import { TokenInterceptor } from './shared/token.interceptor';
|
|
|
|
|
|
|
|
|
13 |
|
14 |
@NgModule({
|
15 |
declarations: [
|
@@ -18,12 +24,18 @@
|
|
18 |
BookListComponent,
|
19 |
BookListItemComponent,
|
20 |
BookDetailsComponent,
|
21 |
-
SearchComponent
|
|
|
|
|
|
|
|
|
22 |
],
|
23 |
imports: [
|
24 |
CommonModule,
|
25 |
HttpClientModule,
|
26 |
-
AppRoutingModule
|
|
|
|
|
27 |
],
|
28 |
providers: [
|
29 |
{ provide: HTTP_INTERCEPTORS, useClass: TokenInterceptor, multi: true }
|
1 |
import { CommonModule } from '@angular/common';
|
2 |
import { NgModule } from '@angular/core';
|
3 |
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
|
4 |
+
import { ReactiveFormsModule } from '@angular/forms';
|
5 |
+
import { DateValueAccessorModule } from 'angular-date-value-accessor';
|
6 |
|
7 |
import { AppRoutingModule } from './app-routing.module.one-app';
|
8 |
import { AppComponent } from './app.component';
|
12 |
import { BookDetailsComponent } from './book-details/book-details.component';
|
13 |
import { SearchComponent } from './search/search.component';
|
14 |
import { TokenInterceptor } from './shared/token.interceptor';
|
15 |
+
import { BookFormComponent } from './book-form/book-form.component';
|
16 |
+
import { CreateBookComponent } from './create-book/create-book.component';
|
17 |
+
import { FormMessagesComponent } from './form-messages/form-messages.component';
|
18 |
+
import { EditBookComponent } from './edit-book/edit-book.component';
|
19 |
|
20 |
@NgModule({
|
21 |
declarations: [
|
24 |
BookListComponent,
|
25 |
BookListItemComponent,
|
26 |
BookDetailsComponent,
|
27 |
+
SearchComponent,
|
28 |
+
BookFormComponent,
|
29 |
+
CreateBookComponent,
|
30 |
+
FormMessagesComponent,
|
31 |
+
EditBookComponent,
|
32 |
],
|
33 |
imports: [
|
34 |
CommonModule,
|
35 |
HttpClientModule,
|
36 |
+
AppRoutingModule,
|
37 |
+
ReactiveFormsModule,
|
38 |
+
DateValueAccessorModule
|
39 |
],
|
40 |
providers: [
|
41 |
{ provide: HTTP_INTERCEPTORS, useClass: TokenInterceptor, multi: true }
|
@@ -33,6 +33,10 @@
|
|
33 |
(click)="removeBook()">
|
34 |
<i class="remove icon"></i> Buch löschen
|
35 |
</button>
|
|
|
|
|
|
|
|
|
36 |
</div>
|
37 |
|
38 |
<ng-template #loading>
|
33 |
(click)="removeBook()">
|
34 |
<i class="remove icon"></i> Buch löschen
|
35 |
</button>
|
36 |
+
<a class="ui tiny yellow labeled icon button"
|
37 |
+
[routerLink]="['../../admin/edit', book.isbn]">
|
38 |
+
<i class="write icon"></i> Buch bearbeiten
|
39 |
+
</a>
|
40 |
</div>
|
41 |
|
42 |
<ng-template #loading>
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<form class="ui form"
|
2 |
+
[formGroup]="bookForm"
|
3 |
+
(ngSubmit)="submitForm()">
|
4 |
+
|
5 |
+
<label>Buchtitel</label>
|
6 |
+
<input formControlName="title">
|
7 |
+
<bm-form-messages
|
8 |
+
[control]="bookForm.get('title')"
|
9 |
+
controlName="title">
|
10 |
+
</bm-form-messages>
|
11 |
+
|
12 |
+
<label>Untertitel</label>
|
13 |
+
<input formControlName="subtitle">
|
14 |
+
|
15 |
+
<label>ISBN</label>
|
16 |
+
<input formControlName="isbn">
|
17 |
+
<bm-form-messages
|
18 |
+
[control]="bookForm.get('isbn')"
|
19 |
+
controlName="isbn">
|
20 |
+
</bm-form-messages>
|
21 |
+
|
22 |
+
<label>Erscheinungsdatum</label>
|
23 |
+
<input type="date"
|
24 |
+
useValueAsDate
|
25 |
+
formControlName="published">
|
26 |
+
<bm-form-messages
|
27 |
+
[control]="bookForm.get('published')"
|
28 |
+
controlName="published">
|
29 |
+
</bm-form-messages>
|
30 |
+
|
31 |
+
<label>Autoren</label>
|
32 |
+
<button type="button" class="ui mini button"
|
33 |
+
(click)="addAuthorControl()">
|
34 |
+
+ Autor
|
35 |
+
</button>
|
36 |
+
<div class="fields" formArrayName="authors">
|
37 |
+
<div class="sixteen wide field"
|
38 |
+
*ngFor="let c of authors.controls; index as i">
|
39 |
+
<input placeholder="Autor"
|
40 |
+
[formControlName]="i">
|
41 |
+
</div>
|
42 |
+
</div>
|
43 |
+
<bm-form-messages
|
44 |
+
[control]="bookForm.get('authors')"
|
45 |
+
controlName="authors">
|
46 |
+
</bm-form-messages>
|
47 |
+
|
48 |
+
<label>Beschreibung</label>
|
49 |
+
<textarea formControlName="description"></textarea>
|
50 |
+
|
51 |
+
<label>Bilder</label>
|
52 |
+
<button type="button" class="ui mini button"
|
53 |
+
(click)="addThumbnailControl()">
|
54 |
+
+ Bild
|
55 |
+
</button>
|
56 |
+
<div formArrayName="thumbnails">
|
57 |
+
<div class="fields"
|
58 |
+
*ngFor="let c of thumbnails.controls; index as i"
|
59 |
+
[formGroupName]="i">
|
60 |
+
<div class="nine wide field">
|
61 |
+
<input placeholder="URL"
|
62 |
+
formControlName="url">
|
63 |
+
</div>
|
64 |
+
<div class="seven wide field">
|
65 |
+
<input placeholder="Titel"
|
66 |
+
formControlName="title">
|
67 |
+
</div>
|
68 |
+
</div>
|
69 |
+
</div>
|
70 |
+
|
71 |
+
<button class="ui button" type="submit"
|
72 |
+
[disabled]="bookForm.invalid">
|
73 |
+
Speichern
|
74 |
+
</button>
|
75 |
+
</form>
|
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Component, OnInit, Input, Output, EventEmitter, OnChanges } from '@angular/core';
|
2 |
+
import { FormBuilder, FormGroup, FormArray, Validators, AbstractControl } from '@angular/forms';
|
3 |
+
|
4 |
+
import { Book, Thumbnail } from '../shared/book';
|
5 |
+
import { BookValidators } from '../shared/book-validators';
|
6 |
+
import { BookExistsValidatorService } from '../shared/book-exists-validator.service';
|
7 |
+
|
8 |
+
@Component({
|
9 |
+
selector: 'bm-book-form',
|
10 |
+
templateUrl: './book-form.component.html',
|
11 |
+
styleUrls: ['./book-form.component.css']
|
12 |
+
})
|
13 |
+
export class BookFormComponent implements OnInit, OnChanges {
|
14 |
+
|
15 |
+
bookForm: FormGroup;
|
16 |
+
|
17 |
+
@Input() book?: Book;
|
18 |
+
|
19 |
+
@Input() set editing(isEditing: boolean) {
|
20 |
+
const isbnControl = this.bookForm.get('isbn')!;
|
21 |
+
if (isEditing) {
|
22 |
+
isbnControl.clearAsyncValidators();
|
23 |
+
isbnControl.disable();
|
24 |
+
} else {
|
25 |
+
isbnControl.setAsyncValidators(control => this.bookExistsValidator.validate(control));
|
26 |
+
isbnControl.enable();
|
27 |
+
}
|
28 |
+
};
|
29 |
+
@Output() submitBook = new EventEmitter<Book>();
|
30 |
+
|
31 |
+
constructor(
|
32 |
+
private fb: FormBuilder,
|
33 |
+
private bookExistsValidator: BookExistsValidatorService
|
34 |
+
) {
|
35 |
+
this.bookForm = this.fb.group({
|
36 |
+
title: ['', Validators.required],
|
37 |
+
subtitle: [''],
|
38 |
+
isbn: ['',
|
39 |
+
[
|
40 |
+
Validators.required,
|
41 |
+
BookValidators.isbnFormat
|
42 |
+
],
|
43 |
+
(control: AbstractControl) => this.bookExistsValidator.validate(control)
|
44 |
+
],
|
45 |
+
description: [''],
|
46 |
+
authors: this.buildAuthorsArray(['']),
|
47 |
+
thumbnails: this.buildThumbnailsArray([
|
48 |
+
{ title: '', url: '' }
|
49 |
+
]),
|
50 |
+
published: [new Date(), [Validators.required]]
|
51 |
+
});
|
52 |
+
}
|
53 |
+
|
54 |
+
ngOnInit(): void { }
|
55 |
+
|
56 |
+
ngOnChanges() {
|
57 |
+
if (this.book) {
|
58 |
+
this.setFormValues(this.book);
|
59 |
+
}
|
60 |
+
}
|
61 |
+
|
62 |
+
private setFormValues(book: Book) {
|
63 |
+
this.bookForm.patchValue(book);
|
64 |
+
|
65 |
+
this.bookForm.setControl(
|
66 |
+
'authors',
|
67 |
+
this.buildAuthorsArray(book.authors)
|
68 |
+
);
|
69 |
+
|
70 |
+
if (book.thumbnails) {
|
71 |
+
this.bookForm.setControl(
|
72 |
+
'thumbnails',
|
73 |
+
this.buildThumbnailsArray(book.thumbnails)
|
74 |
+
);
|
75 |
+
};
|
76 |
+
}
|
77 |
+
|
78 |
+
private buildAuthorsArray(values: string[]): FormArray {
|
79 |
+
return this.fb.array(values, BookValidators.atLeastOneAuthor);
|
80 |
+
}
|
81 |
+
|
82 |
+
private buildThumbnailsArray(values: Thumbnail[]): FormArray {
|
83 |
+
return this.fb.array(
|
84 |
+
values.map(t => this.fb.group(t))
|
85 |
+
);
|
86 |
+
}
|
87 |
+
|
88 |
+
get authors(): FormArray {
|
89 |
+
return this.bookForm.get('authors') as FormArray;
|
90 |
+
}
|
91 |
+
|
92 |
+
get thumbnails(): FormArray {
|
93 |
+
return this.bookForm.get('thumbnails') as FormArray;
|
94 |
+
}
|
95 |
+
|
96 |
+
addAuthorControl() {
|
97 |
+
this.authors.push(this.fb.control(''));
|
98 |
+
}
|
99 |
+
|
100 |
+
addThumbnailControl() {
|
101 |
+
this.thumbnails.push(
|
102 |
+
this.fb.group({ url: '', title: '' })
|
103 |
+
);
|
104 |
+
}
|
105 |
+
|
106 |
+
submitForm() {
|
107 |
+
const formValue = this.bookForm.value;
|
108 |
+
const authors = formValue.authors
|
109 |
+
.filter((author: string) => author);
|
110 |
+
const thumbnails = formValue.thumbnails
|
111 |
+
.filter((thumbnail: Thumbnail) => thumbnail.url);
|
112 |
+
|
113 |
+
const isbn = this.book ? this.book.isbn : formValue.isbn;
|
114 |
+
|
115 |
+
const newBook: Book = {
|
116 |
+
...formValue,
|
117 |
+
isbn,
|
118 |
+
authors,
|
119 |
+
thumbnails
|
120 |
+
};
|
121 |
+
|
122 |
+
this.submitBook.emit(newBook);
|
123 |
+
this.bookForm.reset();
|
124 |
+
}
|
125 |
+
}
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
1 |
+
<h1>Buch hinzufügen</h1>
|
2 |
+
|
3 |
+
<bm-book-form (submitBook)="createBook($event)"></bm-book-form>
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Component, OnInit } from '@angular/core';
|
2 |
+
import { ActivatedRoute, Router } from '@angular/router';
|
3 |
+
|
4 |
+
import { Book } from '../shared/book';
|
5 |
+
import { BookStoreService } from '../shared/book-store.service';
|
6 |
+
|
7 |
+
@Component({
|
8 |
+
selector: 'bm-create-book',
|
9 |
+
templateUrl: './create-book.component.html',
|
10 |
+
styleUrls: ['./create-book.component.css']
|
11 |
+
})
|
12 |
+
export class CreateBookComponent implements OnInit {
|
13 |
+
|
14 |
+
constructor(
|
15 |
+
private bs: BookStoreService,
|
16 |
+
private route: ActivatedRoute,
|
17 |
+
private router: Router
|
18 |
+
) { }
|
19 |
+
|
20 |
+
ngOnInit(): void {
|
21 |
+
}
|
22 |
+
|
23 |
+
createBook(book: Book) {
|
24 |
+
this.bs.create(book).subscribe(() => {
|
25 |
+
this.router.navigate(['../..', 'books'], { relativeTo: this.route });
|
26 |
+
});
|
27 |
+
}
|
28 |
+
|
29 |
+
}
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<h1>Buch bearbeiten</h1>
|
2 |
+
|
3 |
+
<bm-book-form
|
4 |
+
*ngIf="book"
|
5 |
+
(submitBook)="updateBook($event)"
|
6 |
+
[book]="book"
|
7 |
+
[editing]="true"
|
8 |
+
></bm-book-form>
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Component, OnInit } from '@angular/core';
|
2 |
+
import { ActivatedRoute, Router } from '@angular/router';
|
3 |
+
import { map, switchMap } from 'rxjs/operators';
|
4 |
+
|
5 |
+
import { Book } from '../shared/book';
|
6 |
+
import { BookStoreService } from '../shared/book-store.service';
|
7 |
+
|
8 |
+
@Component({
|
9 |
+
selector: 'bm-edit-book',
|
10 |
+
templateUrl: './edit-book.component.html',
|
11 |
+
styleUrls: ['./edit-book.component.css']
|
12 |
+
})
|
13 |
+
export class EditBookComponent implements OnInit {
|
14 |
+
|
15 |
+
book?: Book;
|
16 |
+
|
17 |
+
constructor(
|
18 |
+
private bs: BookStoreService,
|
19 |
+
private route: ActivatedRoute,
|
20 |
+
private router: Router
|
21 |
+
) { }
|
22 |
+
|
23 |
+
ngOnInit(): void {
|
24 |
+
this.route.paramMap.pipe(
|
25 |
+
map(params => params.get('isbn') || ''),
|
26 |
+
switchMap(isbn => this.bs.getSingle(isbn))
|
27 |
+
)
|
28 |
+
.subscribe(book => this.book = book);
|
29 |
+
}
|
30 |
+
|
31 |
+
updateBook(book: Book) {
|
32 |
+
this.bs.update(book).subscribe(() => {
|
33 |
+
this.router.navigate(['../../..', 'books', book.isbn], { relativeTo: this.route });
|
34 |
+
});
|
35 |
+
}
|
36 |
+
|
37 |
+
}
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
1 |
+
<div class="ui negative message"
|
2 |
+
*ngFor="let msg of errorsForControl()">
|
3 |
+
{{ msg }}
|
4 |
+
</div>
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Component, OnInit, Input } from '@angular/core';
|
2 |
+
import { AbstractControl } from '@angular/forms';
|
3 |
+
|
4 |
+
@Component({
|
5 |
+
selector: 'bm-form-messages',
|
6 |
+
templateUrl: './form-messages.component.html',
|
7 |
+
styleUrls: ['./form-messages.component.css']
|
8 |
+
})
|
9 |
+
export class FormMessagesComponent implements OnInit {
|
10 |
+
|
11 |
+
@Input() control?: AbstractControl | null;
|
12 |
+
@Input() controlName?: string;
|
13 |
+
|
14 |
+
private allMessages: { [key: string]: { [key: string]: string } } = {
|
15 |
+
title: {
|
16 |
+
required: 'Ein Buchtitel muss angegeben werden.'
|
17 |
+
},
|
18 |
+
isbn: {
|
19 |
+
required: 'Es muss eine ISBN angegeben werden.',
|
20 |
+
isbnFormat: 'Die ISBN muss aus 10 oder 13 Zeichen bestehen.',
|
21 |
+
isbnExists: 'Die ISBN existiert bereits.'
|
22 |
+
},
|
23 |
+
published: {
|
24 |
+
required: 'Es muss ein Erscheinungsdatum angegeben werden.'
|
25 |
+
},
|
26 |
+
authors: {
|
27 |
+
atLeastOneAuthor: 'Es muss ein Autor angegeben werden.'
|
28 |
+
}
|
29 |
+
};
|
30 |
+
|
31 |
+
constructor() { }
|
32 |
+
|
33 |
+
ngOnInit(): void {
|
34 |
+
}
|
35 |
+
|
36 |
+
errorsForControl(): string[] {
|
37 |
+
type allMessagesKey = keyof FormMessagesComponent['allMessages'];
|
38 |
+
const messages = this.allMessages[this.controlName as keyof allMessagesKey];
|
39 |
+
|
40 |
+
if (
|
41 |
+
!this.control ||
|
42 |
+
!this.control.errors ||
|
43 |
+
!messages ||
|
44 |
+
!this.control.dirty
|
45 |
+
) { return []; }
|
46 |
+
|
47 |
+
return Object.keys(this.control.errors)
|
48 |
+
.map(err => messages[err]);
|
49 |
+
}
|
50 |
+
|
51 |
+
}
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Injectable } from '@angular/core';
|
2 |
+
import { AsyncValidator, ValidationErrors, AbstractControl } from '@angular/forms';
|
3 |
+
import { Observable, of } from 'rxjs';
|
4 |
+
import { map, catchError } from 'rxjs/operators';
|
5 |
+
|
6 |
+
import { BookStoreService } from './book-store.service';
|
7 |
+
|
8 |
+
@Injectable({
|
9 |
+
providedIn: 'root'
|
10 |
+
})
|
11 |
+
export class BookExistsValidatorService implements AsyncValidator {
|
12 |
+
|
13 |
+
constructor(private bs: BookStoreService) { }
|
14 |
+
|
15 |
+
validate(
|
16 |
+
control: AbstractControl
|
17 |
+
): Observable<ValidationErrors | null> {
|
18 |
+
return this.bs.check(control.value).pipe(
|
19 |
+
map(exists => (exists === false) ? null : {
|
20 |
+
isbnExists: { valid: false }
|
21 |
+
}),
|
22 |
+
catchError(() => of(null))
|
23 |
+
);
|
24 |
+
}
|
25 |
+
}
|
@@ -2,6 +2,7 @@
|
|
2 |
import { BookRaw } from './book-raw';
|
3 |
|
4 |
export class BookFactory {
|
|
|
5 |
static fromRaw(b: BookRaw): Book {
|
6 |
return {
|
7 |
...b,
|
2 |
import { BookRaw } from './book-raw';
|
3 |
|
4 |
export class BookFactory {
|
5 |
+
|
6 |
static fromRaw(b: BookRaw): Book {
|
7 |
return {
|
8 |
...b,
|
@@ -36,6 +36,26 @@
|
|
36 |
);
|
37 |
}
|
38 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
39 |
remove(isbn: string): Observable<any> {
|
40 |
return this.http.delete(
|
41 |
`${this.api}/book/${isbn}`,
|
@@ -56,6 +76,14 @@
|
|
56 |
catchError(this.errorHandler)
|
57 |
);
|
58 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
59 |
|
60 |
private errorHandler(error: HttpErrorResponse): Observable<any> {
|
61 |
console.error('Fehler aufgetreten!');
|
36 |
);
|
37 |
}
|
38 |
|
39 |
+
create(book: Book): Observable<any> {
|
40 |
+
return this.http.post(
|
41 |
+
`${this.api}/book`,
|
42 |
+
book,
|
43 |
+
{ responseType: 'text' }
|
44 |
+
).pipe(
|
45 |
+
catchError(this.errorHandler)
|
46 |
+
);
|
47 |
+
}
|
48 |
+
|
49 |
+
update(book: Book): Observable<any> {
|
50 |
+
return this.http.put(
|
51 |
+
`${this.api}/book/${book.isbn}`,
|
52 |
+
book,
|
53 |
+
{ responseType: 'text' }
|
54 |
+
).pipe(
|
55 |
+
catchError(this.errorHandler)
|
56 |
+
);
|
57 |
+
}
|
58 |
+
|
59 |
remove(isbn: string): Observable<any> {
|
60 |
return this.http.delete(
|
61 |
`${this.api}/book/${isbn}`,
|
76 |
catchError(this.errorHandler)
|
77 |
);
|
78 |
}
|
79 |
+
|
80 |
+
check(isbn: string): Observable<boolean> {
|
81 |
+
return this.http.get<boolean>(
|
82 |
+
`${this.api}/book/${isbn}/check`
|
83 |
+
).pipe(
|
84 |
+
catchError(this.errorHandler)
|
85 |
+
);
|
86 |
+
}
|
87 |
|
88 |
private errorHandler(error: HttpErrorResponse): Observable<any> {
|
89 |
console.error('Fehler aufgetreten!');
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { FormArray, ValidationErrors, AbstractControl } from '@angular/forms';
|
2 |
+
|
3 |
+
export class BookValidators {
|
4 |
+
|
5 |
+
static isbnFormat(control: AbstractControl): ValidationErrors | null {
|
6 |
+
if (!control.value) { return null; }
|
7 |
+
|
8 |
+
const numbers = control.value.replace(/-/g, '');
|
9 |
+
const isbnPattern = /(^\d{10}$)|(^\d{13}$)/;
|
10 |
+
|
11 |
+
if (isbnPattern.test(numbers)) {
|
12 |
+
return null;
|
13 |
+
} else {
|
14 |
+
return {
|
15 |
+
isbnFormat: { valid: false }
|
16 |
+
};
|
17 |
+
}
|
18 |
+
}
|
19 |
+
|
20 |
+
|
21 |
+
static atLeastOneAuthor(controlArray: AbstractControl): ValidationErrors | null {
|
22 |
+
if ((controlArray as FormArray).controls.some((el: AbstractControl) => el.value)) {
|
23 |
+
return null;
|
24 |
+
} else {
|
25 |
+
return {
|
26 |
+
atLeastOneAuthor: { valid: false }
|
27 |
+
};
|
28 |
+
}
|
29 |
+
}
|
30 |
+
|
31 |
+
}
|