BookMonkey 4 Diff

Files changed (12) hide show
  1. tmp/src/app/book-monkey/iteration-4/{template-driven-forms → reactive-forms}/app-routing.module.ts +5 -0
  2. tmp/src/app/book-monkey/iteration-4/{template-driven-forms → reactive-forms}/app.module.ts +5 -3
  3. tmp/src/app/book-monkey/iteration-4/{template-driven-forms → reactive-forms}/book-details/book-details.component.html +4 -0
  4. tmp/src/app/book-monkey/iteration-4/{template-driven-forms → reactive-forms}/book-form/book-form.component.html +42 -52
  5. tmp/src/app/book-monkey/iteration-4/{template-driven-forms → reactive-forms}/book-form/book-form.component.ts +106 -13
  6. tmp/src/app/book-monkey/iteration-4/{template-driven-forms → reactive-forms}/create-book/create-book.component.html +2 -2
  7. tmp/src/app/book-monkey/iteration-4/{template-driven-forms → reactive-forms}/create-book/create-book.component.ts +1 -0
  8. tmp/src/app/book-monkey/iteration-4/{template-driven-forms → reactive-forms}/edit-book/edit-book.component.html +8 -0
  9. tmp/src/app/book-monkey/iteration-4/{template-driven-forms → reactive-forms}/edit-book/edit-book.component.ts +38 -0
  10. tmp/src/app/book-monkey/iteration-4/{template-driven-forms → reactive-forms}/form-messages/form-messages.component.ts +0 -1
  11. tmp/src/app/book-monkey/iteration-4/{template-driven-forms → reactive-forms}/shared/book-factory.ts +0 -15
  12. tmp/src/app/book-monkey/iteration-4/{template-driven-forms → reactive-forms}/shared/book-store.service.ts +10 -0
tmp/src/app/book-monkey/iteration-4/{template-driven-forms → reactive-forms}/app-routing.module.ts RENAMED
@@ -5,6 +5,7 @@
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
   
9
  export const routes: Routes = [
10
  {
@@ -32,6 +33,10 @@
32
  {
33
  path: 'admin/create',
34
  component: CreateBookComponent
   
   
   
   
35
  }
36
  ];
37
   
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
  {
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-4/{template-driven-forms → reactive-forms}/app.module.ts RENAMED
@@ -1,7 +1,7 @@
1
  import { CommonModule } from '@angular/common';
2
  import { NgModule } from '@angular/core';
3
  import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
4
- import { FormsModule } from '@angular/forms';
5
  import { DateValueAccessorModule } from 'angular-date-value-accessor';
6
   
7
  import { AppRoutingModule } from './app-routing.module.one-app';
@@ -15,6 +15,7 @@
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
   
19
  @NgModule({
20
  declarations: [
@@ -26,13 +27,14 @@
26
  SearchComponent,
27
  BookFormComponent,
28
  CreateBookComponent,
29
- FormMessagesComponent
   
30
  ],
31
  imports: [
32
  CommonModule,
33
  HttpClientModule,
34
  AppRoutingModule,
35
- FormsModule,
36
  DateValueAccessorModule
37
  ],
38
  providers: [
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';
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: [
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: [
tmp/src/app/book-monkey/iteration-4/{template-driven-forms → reactive-forms}/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-4/{template-driven-forms → reactive-forms}/book-form/book-form.component.html RENAMED
@@ -1,80 +1,70 @@
1
  <form class="ui form"
2
- (ngSubmit)="submitForm()"
3
- #bookForm="ngForm">
4
   
5
  <label>Buchtitel</label>
6
- <input
7
- name="title"
8
- [(ngModel)]="book.title"
9
- required
10
- #titleInput="ngModel">
11
-
12
  <bm-form-messages
13
- [control]="titleInput.control"
14
  controlName="title">
15
  </bm-form-messages>
16
   
17
  <label>Untertitel</label>
18
- <input
19
- name="subtitle"
20
- [(ngModel)]="book.subtitle">
21
   
22
  <label>ISBN</label>
23
- <input
24
- name="isbn"
25
- [(ngModel)]="book.isbn"
26
- required
27
- minlength="10"
28
- maxlength="13"
29
- #isbnInput="ngModel">
30
  <bm-form-messages
31
- [control]="isbnInput.control"
32
  controlName="isbn">
33
  </bm-form-messages>
34
   
35
  <label>Erscheinungsdatum</label>
36
- <input
37
- type="date"
38
- name="published"
39
- [(ngModel)]="book.published"
40
  useValueAsDate
41
- required
42
- #dateInput="ngModel">
43
  <bm-form-messages
44
- [control]="dateInput.control"
45
  controlName="published">
46
  </bm-form-messages>
47
   
48
- <label>Autor</label>
49
- <input
50
- name="authors"
51
- [(ngModel)]="book.authors[0]"
52
- required
53
- #authorInput="ngModel">
   
   
   
   
   
   
54
  <bm-form-messages
55
- [control]="authorInput.control"
56
  controlName="authors">
57
  </bm-form-messages>
58
   
59
  <label>Beschreibung</label>
60
- <textarea
61
- name="description"
62
- [(ngModel)]="book.description"></textarea>
63
-  
64
- <label>Bild</label>
65
-
66
- <div class="two fields">
67
- <div class="field">
68
- <input
69
- name="url"
70
- [(ngModel)]="$any(book).thumbnails[0].url"
71
- placeholder="URL">
72
- </div>
73
- <div class="field">
74
- <input
75
- name="title"
76
- [(ngModel)]="$any(book).thumbnails[0].title"
77
- placeholder="Titel">
   
78
  </div>
79
  </div>
80
   
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
   
tmp/src/app/book-monkey/iteration-4/{template-driven-forms → reactive-forms}/book-form/book-form.component.ts RENAMED
@@ -1,30 +1,123 @@
1
- import { Component, ViewChild, OnInit, Output, EventEmitter } from '@angular/core';
2
- import { NgForm } from '@angular/forms';
3
   
4
- import { Book } from '../shared/book';
5
- import { BookFactory } from '../shared/book-factory';
6
   
7
  @Component({
8
  selector: 'bm-book-form',
9
  templateUrl: './book-form.component.html',
10
  styleUrls: ['./book-form.component.css']
11
  })
12
- export class BookFormComponent implements OnInit {
13
   
14
- book = BookFactory.empty();
15
   
16
- @Output() submitBook = new EventEmitter<Book>();
17
-
18
- @ViewChild('bookForm', { static: true }) form?: NgForm;
19
   
20
 
21
- submitForm() {
22
- this.submitBook.emit(this.book);
   
   
   
   
   
   
23
   
24
- this.book = BookFactory.empty();
25
- this.form?.reset();
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
26
  }
27
   
28
  ngOnInit(): void {
29
  }
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
30
  }
1
+ import { Component, OnInit, Input, Output, EventEmitter, OnChanges } from '@angular/core';
2
+ import { FormBuilder, FormGroup, FormArray, Validators } from '@angular/forms';
3
   
4
+ import { Book, Thumbnail } from '../shared/book';
   
5
   
6
  @Component({
7
  selector: 'bm-book-form',
8
  templateUrl: './book-form.component.html',
9
  styleUrls: ['./book-form.component.css']
10
  })
11
+ export class BookFormComponent implements OnInit, OnChanges {
12
   
13
+ bookForm: FormGroup;
14
   
15
+ @Input() book?: Book;
   
   
16
   
17
 
18
+ @Input() set editing(isEditing: boolean) {
19
+ const isbnControl = this.bookForm.get('isbn')!;
20
+ if (isEditing) {
21
+ isbnControl.disable();
22
+ } else {
23
+ isbnControl.enable();
24
+ }
25
+ };
26
   
27
+ @Output() submitBook = new EventEmitter<Book>();
28
+  
29
+ constructor(private fb: FormBuilder) {
30
+
31
+ this.bookForm = this.fb.group({
32
+ title: ['', Validators.required],
33
+ subtitle: [''],
34
+ isbn: ['', [
35
+ Validators.required,
36
+ Validators.minLength(10),
37
+ Validators.maxLength(13)
38
+ ]],
39
+ description: [''],
40
+ authors: this.buildAuthorsArray(['']),
41
+ thumbnails: this.buildThumbnailsArray([
42
+ { title: '', url: '' }
43
+ ]),
44
+ published: [new Date(), [Validators.required]]
45
+ });
46
  }
47
   
48
  ngOnInit(): void {
49
  }
50
+  
51
+ ngOnChanges() {
52
+  
53
+ if (this.book) {
54
+ this.setFormValues(this.book);
55
+ }
56
+ }
57
+  
58
+ private setFormValues(book: Book) {
59
+ this.bookForm.patchValue(book);
60
+  
61
+ this.bookForm.setControl(
62
+ 'authors',
63
+ this.buildAuthorsArray(book.authors)
64
+ );
65
+  
66
+ if (book.thumbnails) {
67
+ this.bookForm.setControl(
68
+ 'thumbnails',
69
+ this.buildThumbnailsArray(book.thumbnails)
70
+ );
71
+ };
72
+ }
73
+  
74
+  
75
+ private buildAuthorsArray(values: string[]): FormArray {
76
+ return this.fb.array(values, Validators.required);
77
+ }
78
+  
79
+ private buildThumbnailsArray(values: Thumbnail[]): FormArray {
80
+ return this.fb.array(
81
+ values.map(t => this.fb.group(t))
82
+ );
83
+ }
84
+  
85
+ get authors(): FormArray {
86
+ return this.bookForm.get('authors') as FormArray;
87
+ }
88
+  
89
+ get thumbnails(): FormArray {
90
+ return this.bookForm.get('thumbnails') as FormArray;
91
+ }
92
+  
93
+ addAuthorControl() {
94
+ this.authors.push(this.fb.control(''));
95
+ }
96
+  
97
+ addThumbnailControl() {
98
+ this.thumbnails.push(
99
+ this.fb.group({ url: '', title: '' })
100
+ );
101
+ }
102
+  
103
+ submitForm() {
104
+ const formValue = this.bookForm.value;
105
+
106
+ const authors = formValue.authors
107
+ .filter((author: string) => author);
108
+ const thumbnails = formValue.thumbnails
109
+ .filter((thumbnail: Thumbnail) => thumbnail.url);
110
+  
111
+ const isbn = this.book ? this.book.isbn : formValue.isbn;
112
+  
113
+ const newBook: Book = {
114
+ ...formValue,
115
+ isbn,
116
+ authors,
117
+ thumbnails
118
+ };
119
+  
120
+ this.submitBook.emit(newBook);
121
+ this.bookForm.reset();
122
+ }
123
  }
tmp/src/app/book-monkey/iteration-4/{template-driven-forms → reactive-forms}/create-book/create-book.component.html RENAMED
@@ -1,3 +1,3 @@
1
  <h1>Buch hinzufügen</h1>
2
- <bm-book-form (submitBook)="createBook($event)">
3
- </bm-book-form>
1
  <h1>Buch hinzufügen</h1>
2
+  
3
+ <bm-book-form (submitBook)="createBook($event)"></bm-book-form>
tmp/src/app/book-monkey/iteration-4/{template-driven-forms → reactive-forms}/create-book/create-book.component.ts RENAMED
@@ -1,5 +1,6 @@
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';
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';
tmp/src/app/book-monkey/iteration-4/{template-driven-forms → reactive-forms}/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-4/{template-driven-forms → reactive-forms}/edit-book/edit-book.component.ts RENAMED
@@ -0,0 +1,38 @@
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
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
+
25
+ this.route.paramMap.pipe(
26
+ map(params => params.get('isbn') || ''),
27
+ switchMap(isbn => this.bs.getSingle(isbn))
28
+ )
29
+ .subscribe(book => this.book = book);
30
+ }
31
+  
32
+ updateBook(book: Book) {
33
+ this.bs.update(book).subscribe(() => {
34
+ this.router.navigate(['../../..', 'books', book.isbn], { relativeTo: this.route });
35
+ });
36
+ }
37
+  
38
+ }
tmp/src/app/book-monkey/iteration-4/{template-driven-forms → reactive-forms}/form-messages/form-messages.component.ts RENAMED
@@ -8,7 +8,6 @@
8
  })
9
  export class FormMessagesComponent implements OnInit {
10
   
11
-
12
  @Input() control?: AbstractControl | null;
13
  @Input() controlName?: string;
14
   
8
  })
9
  export class FormMessagesComponent implements OnInit {
10
   
   
11
  @Input() control?: AbstractControl | null;
12
  @Input() controlName?: string;
13
   
tmp/src/app/book-monkey/iteration-4/{template-driven-forms → reactive-forms}/shared/book-factory.ts RENAMED
@@ -3,21 +3,6 @@
3
   
4
  export class BookFactory {
5
   
6
- static empty(): Book {
7
- return {
8
- isbn: '',
9
- title: '',
10
- authors: [''],
11
- published: new Date(),
12
- subtitle: '',
13
- rating: 0,
14
- thumbnails: [
15
- { url: '', title: '' }
16
- ],
17
- description: ''
18
- };
19
- }
20
-  
21
  static fromRaw(b: BookRaw): Book {
22
  return {
23
  ...b,
3
   
4
  export class BookFactory {
5
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
6
  static fromRaw(b: BookRaw): Book {
7
  return {
8
  ...b,
tmp/src/app/book-monkey/iteration-4/{template-driven-forms → reactive-forms}/shared/book-store.service.ts RENAMED
@@ -46,6 +46,16 @@
46
  );
47
  }
48
   
   
   
   
   
   
   
   
   
   
   
49
  remove(isbn: string): Observable<any> {
50
  return this.http.delete(
51
  `${this.api}/book/${isbn}`,
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}`,