BookMonkey 4 Diff

Files changed (16) hide show
  1. tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/custom-validation}/app-routing.module.ts +15 -0
  2. tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/custom-validation}/app.component.html +1 -0
  3. tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/custom-validation}/app.module.ts +14 -2
  4. tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/custom-validation}/book-details/book-details.component.html +4 -0
  5. tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/custom-validation}/book-form/book-form.component.html +75 -0
  6. tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/custom-validation}/book-form/book-form.component.ts +125 -0
  7. tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/custom-validation}/create-book/create-book.component.html +3 -0
  8. tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/custom-validation}/create-book/create-book.component.ts +29 -0
  9. tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/custom-validation}/edit-book/edit-book.component.html +8 -0
  10. tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/custom-validation}/edit-book/edit-book.component.ts +37 -0
  11. tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/custom-validation}/form-messages/form-messages.component.html +4 -0
  12. tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/custom-validation}/form-messages/form-messages.component.ts +51 -0
  13. tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/custom-validation}/shared/book-exists-validator.service.ts +25 -0
  14. tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/custom-validation}/shared/book-factory.ts +1 -0
  15. tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/custom-validation}/shared/book-store.service.ts +28 -0
  16. tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/custom-validation}/shared/book-validators.ts +31 -0
tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/custom-validation}/app-routing.module.ts RENAMED
@@ -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
   
tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/custom-validation}/app.component.html RENAMED
@@ -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>
tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/custom-validation}/app.module.ts RENAMED
@@ -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 }
tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/custom-validation}/book-details/book-details.component.html RENAMED
@@ -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>
tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/custom-validation}/book-form/book-form.component.html RENAMED
@@ -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>
tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/custom-validation}/book-form/book-form.component.ts RENAMED
@@ -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
+ }
tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/custom-validation}/create-book/create-book.component.html RENAMED
@@ -0,0 +1,3 @@
   
   
   
1
+ <h1>Buch hinzufügen</h1>
2
+  
3
+ <bm-book-form (submitBook)="createBook($event)"></bm-book-form>
tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/custom-validation}/create-book/create-book.component.ts RENAMED
@@ -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
+ }
tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/custom-validation}/edit-book/edit-book.component.html RENAMED
@@ -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>
tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/custom-validation}/edit-book/edit-book.component.ts RENAMED
@@ -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
+ }
tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/custom-validation}/form-messages/form-messages.component.html RENAMED
@@ -0,0 +1,4 @@
   
   
   
   
1
+ <div class="ui negative message"
2
+ *ngFor="let msg of errorsForControl()">
3
+ {{ msg }}
4
+ </div>
tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/custom-validation}/form-messages/form-messages.component.ts RENAMED
@@ -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
+ }
tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/custom-validation}/shared/book-exists-validator.service.ts RENAMED
@@ -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
+ }
tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/custom-validation}/shared/book-factory.ts RENAMED
@@ -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,
tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/custom-validation}/shared/book-store.service.ts RENAMED
@@ -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!');
tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/custom-validation}/shared/book-validators.ts RENAMED
@@ -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
+ }