BookMonkey 4 Diff

Files changed (11) hide show
  1. tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/template-driven-forms}/app-routing.module.ts +10 -0
  2. tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/template-driven-forms}/app.component.html +1 -0
  3. tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/template-driven-forms}/app.module.ts +12 -2
  4. tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/template-driven-forms}/book-form/book-form.component.html +85 -0
  5. tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/template-driven-forms}/book-form/book-form.component.ts +30 -0
  6. tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/template-driven-forms}/create-book/create-book.component.html +3 -0
  7. tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/template-driven-forms}/create-book/create-book.component.ts +29 -0
  8. tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/template-driven-forms}/form-messages/form-messages.component.html +4 -0
  9. tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/template-driven-forms}/form-messages/form-messages.component.ts +52 -0
  10. tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/template-driven-forms}/shared/book-factory.ts +16 -0
  11. tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/template-driven-forms}/shared/book-store.service.ts +10 -0
tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/template-driven-forms}/app-routing.module.ts RENAMED
@@ -4,6 +4,7 @@
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 +23,15 @@
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
   
9
  export const routes: Routes = [
10
  {
23
  {
24
  path: 'books/:isbn',
25
  component: BookDetailsComponent
26
+ },
27
+ {
28
+ path: 'admin',
29
+ redirectTo: 'admin/create',
30
+ pathMatch: 'full'
31
+ },
32
+ {
33
+ path: 'admin/create',
34
+ component: CreateBookComponent
35
  }
36
  ];
37
   
tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/template-driven-forms}/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/template-driven-forms}/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,9 @@
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 +23,17 @@
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 { FormsModule } 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
   
19
  @NgModule({
20
  declarations: [
23
  BookListComponent,
24
  BookListItemComponent,
25
  BookDetailsComponent,
26
+ SearchComponent,
27
+ BookFormComponent,
28
+ CreateBookComponent,
29
+ FormMessagesComponent
30
  ],
31
  imports: [
32
  CommonModule,
33
  HttpClientModule,
34
+ AppRoutingModule,
35
+ FormsModule,
36
+ DateValueAccessorModule
37
  ],
38
  providers: [
39
  { provide: HTTP_INTERCEPTORS, useClass: TokenInterceptor, multi: true }
tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/template-driven-forms}/book-form/book-form.component.html RENAMED
@@ -0,0 +1,85 @@
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
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
+  
81
+ <button class="ui button" type="submit"
82
+ [disabled]="bookForm.invalid">
83
+ Speichern
84
+ </button>
85
+ </form>
tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/template-driven-forms}/book-form/book-form.component.ts RENAMED
@@ -0,0 +1,30 @@
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
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
+ }
tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/template-driven-forms}/create-book/create-book.component.html RENAMED
@@ -0,0 +1,3 @@
   
   
   
1
+ <h1>Buch hinzufügen</h1>
2
+ <bm-book-form (submitBook)="createBook($event)">
3
+ </bm-book-form>
tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/template-driven-forms}/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/template-driven-forms}/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/template-driven-forms}/form-messages/form-messages.component.ts RENAMED
@@ -0,0 +1,52 @@
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
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
+
12
+ @Input() control?: AbstractControl | null;
13
+ @Input() controlName?: string;
14
+  
15
+ private allMessages: { [key: string]: { [key: string]: string } } = {
16
+ title: {
17
+ required: 'Ein Buchtitel muss angegeben werden.'
18
+ },
19
+ isbn: {
20
+ required: 'Es muss eine ISBN angegeben werden.',
21
+ minlength: 'Die ISBN muss mindestens 10 Zeichen haben.',
22
+ maxlength: 'Die ISBN darf höchstens 13 Zeichen haben.'
23
+ },
24
+ published: {
25
+ required: 'Es muss ein Erscheinungsdatum angegeben werden.'
26
+ },
27
+ authors: {
28
+ required: 'Es muss ein Autor angegeben werden.'
29
+ }
30
+ };
31
+  
32
+ constructor() { }
33
+  
34
+ ngOnInit(): void {
35
+ }
36
+  
37
+ errorsForControl(): string[] {
38
+ type allMessagesKey = keyof FormMessagesComponent['allMessages'];
39
+ const messages = this.allMessages[this.controlName as keyof allMessagesKey];
40
+  
41
+ if (
42
+ !this.control ||
43
+ !this.control.errors ||
44
+ !messages ||
45
+ !this.control.dirty
46
+ ) { return []; }
47
+  
48
+ return Object.keys(this.control.errors)
49
+ .map(err => messages[err]);
50
+ }
51
+  
52
+ }
tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/template-driven-forms}/shared/book-factory.ts RENAMED
@@ -2,6 +2,22 @@
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 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,
tmp/src/app/book-monkey/{iteration-3/interceptors → iteration-4/template-driven-forms}/shared/book-store.service.ts RENAMED
@@ -36,6 +36,16 @@
36
  );
37
  }
38
   
   
   
   
   
   
   
   
   
   
   
39
  remove(isbn: string): Observable<any> {
40
  return this.http.delete(
41
  `${this.api}/book/${isbn}`,
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
  remove(isbn: string): Observable<any> {
50
  return this.http.delete(
51
  `${this.api}/book/${isbn}`,