BookMonkey 4 Diff

Files changed (26) hide show
  1. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/admin/admin-routing.module.ts +28 -0
  2. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/admin/admin.module.ts +26 -0
  3. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/admin/book-form/book-form.component.html +75 -0
  4. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/admin/book-form/book-form.component.ts +124 -0
  5. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/admin/create-book/create-book.component.html +3 -0
  6. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/admin/create-book/create-book.component.ts +29 -0
  7. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/admin/edit-book/edit-book.component.html +8 -0
  8. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/admin/edit-book/edit-book.component.ts +37 -0
  9. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/admin/form-messages/form-messages.component.html +4 -0
  10. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/admin/form-messages/form-messages.component.ts +51 -0
  11. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/admin/shared/book-exists-validator.service.ts +25 -0
  12. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/admin/shared/book-validators.ts +30 -0
  13. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/app-routing.module.ts +4 -19
  14. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/app.module.ts +2 -26
  15. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/books/book-details/book-details.component.html +48 -0
  16. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/books/book-details/book-details.component.ts +37 -0
  17. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/books/book-list/book-list.component.html +19 -0
  18. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/books/book-list/book-list.component.ts +20 -0
  19. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/books/book-list-item/book-list-item.component.html +17 -0
  20. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/books/book-list-item/book-list-item.component.ts +15 -0
  21. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/books/books-routing.module.ts +23 -0
  22. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/books/books.module.ts +26 -0
  23. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/books/shared/delay.directive.ts +20 -0
  24. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/books/shared/isbn.pipe.ts +12 -0
  25. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/books/shared/zoom.directive.ts +15 -0
  26. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/can-navigate-to-admin.guard.ts +17 -0
tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/admin/admin-routing.module.ts RENAMED
@@ -0,0 +1,28 @@
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
1
+ import { NgModule } from '@angular/core';
2
+ import { Routes, RouterModule } from '@angular/router';
3
+  
4
+ import { CreateBookComponent } from './create-book/create-book.component';
5
+ import { EditBookComponent } from './edit-book/edit-book.component';
6
+  
7
+ const routes: Routes = [
8
+ {
9
+ path: '',
10
+ redirectTo: 'create',
11
+ pathMatch: 'full'
12
+ },
13
+ {
14
+ path: 'create',
15
+ component: CreateBookComponent
16
+ },
17
+ {
18
+ path: 'edit/:isbn',
19
+ component: EditBookComponent
20
+ }
21
+ ];
22
+  
23
+ @NgModule({
24
+ imports: [RouterModule.forChild(routes)],
25
+ exports: [RouterModule],
26
+ providers: []
27
+ })
28
+ export class AdminRoutingModule { }
tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/admin/admin.module.ts RENAMED
@@ -0,0 +1,26 @@
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
1
+ import { NgModule } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { ReactiveFormsModule } from '@angular/forms';
4
+ import { DateValueAccessorModule } from 'angular-date-value-accessor';
5
+  
6
+ import { AdminRoutingModule } from './admin-routing.module';
7
+ import { BookFormComponent } from './book-form/book-form.component';
8
+ import { CreateBookComponent } from './create-book/create-book.component';
9
+ import { FormMessagesComponent } from './form-messages/form-messages.component';
10
+ import { EditBookComponent } from './edit-book/edit-book.component';
11
+  
12
+ @NgModule({
13
+ imports: [
14
+ CommonModule,
15
+ AdminRoutingModule,
16
+ ReactiveFormsModule,
17
+ DateValueAccessorModule
18
+ ],
19
+ declarations: [
20
+ BookFormComponent,
21
+ CreateBookComponent,
22
+ EditBookComponent,
23
+ FormMessagesComponent
24
+ ]
25
+ })
26
+ export class AdminModule { }
tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/admin/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-5/directives → iteration-6/guards}/admin/book-form/book-form.component.ts RENAMED
@@ -0,0 +1,124 @@
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
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
+ @Input() set editing(isEditing: boolean) {
19
+ const isbnControl = this.bookForm.get('isbn')!;
20
+ if (isEditing) {
21
+ isbnControl.clearAsyncValidators();
22
+ isbnControl.disable();
23
+ } else {
24
+ isbnControl.setAsyncValidators(control => this.bookExistsValidator.validate(control));
25
+ isbnControl.enable();
26
+ }
27
+ };
28
+ @Output() submitBook = new EventEmitter<Book>();
29
+  
30
+ constructor(
31
+ private fb: FormBuilder,
32
+ private bookExistsValidator: BookExistsValidatorService
33
+ ) {
34
+ this.bookForm = this.fb.group({
35
+ title: ['', Validators.required],
36
+ subtitle: [''],
37
+ isbn: ['',
38
+ [
39
+ Validators.required,
40
+ BookValidators.isbnFormat
41
+ ],
42
+ (control: AbstractControl) => this.bookExistsValidator.validate(control)
43
+ ],
44
+ description: [''],
45
+ authors: this.buildAuthorsArray(['']),
46
+ thumbnails: this.buildThumbnailsArray([
47
+ { title: '', url: '' }
48
+ ]),
49
+ published: [new Date(), [Validators.required]]
50
+ });
51
+ }
52
+  
53
+ ngOnInit(): void {}
54
+  
55
+ ngOnChanges() {
56
+ if (this.book) {
57
+ this.setFormValues(this.book);
58
+ }
59
+ }
60
+  
61
+ private setFormValues(book: Book) {
62
+ this.bookForm.patchValue(book);
63
+  
64
+ this.bookForm.setControl(
65
+ 'authors',
66
+ this.buildAuthorsArray(book.authors)
67
+ );
68
+  
69
+ if (book.thumbnails) {
70
+ this.bookForm.setControl(
71
+ 'thumbnails',
72
+ this.buildThumbnailsArray(book.thumbnails)
73
+ );
74
+ };
75
+ }
76
+  
77
+ private buildAuthorsArray(values: string[]): FormArray {
78
+ return this.fb.array(values, BookValidators.atLeastOneAuthor);
79
+ }
80
+  
81
+ private buildThumbnailsArray(values: Thumbnail[]): FormArray {
82
+ return this.fb.array(
83
+ values.map(t => this.fb.group(t))
84
+ );
85
+ }
86
+  
87
+ get authors(): FormArray {
88
+ return this.bookForm.get('authors') as FormArray;
89
+ }
90
+  
91
+ get thumbnails(): FormArray {
92
+ return this.bookForm.get('thumbnails') as FormArray;
93
+ }
94
+  
95
+ addAuthorControl() {
96
+ this.authors.push(this.fb.control(''));
97
+ }
98
+  
99
+ addThumbnailControl() {
100
+ this.thumbnails.push(
101
+ this.fb.group({ url: '', title: '' })
102
+ );
103
+ }
104
+  
105
+ submitForm() {
106
+ const formValue = this.bookForm.value;
107
+ const authors = formValue.authors
108
+ .filter((author: string) => author);
109
+ const thumbnails = formValue.thumbnails
110
+ .filter((thumbnail: Thumbnail) => thumbnail.url);
111
+  
112
+ const isbn = this.book ? this.book.isbn : formValue.isbn;
113
+  
114
+ const newBook: Book = {
115
+ ...formValue,
116
+ isbn,
117
+ authors,
118
+ thumbnails
119
+ };
120
+  
121
+ this.submitBook.emit(newBook);
122
+ this.bookForm.reset();
123
+ }
124
+ }
tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/admin/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-5/directives → iteration-6/guards}/admin/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-5/directives → iteration-6/guards}/admin/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-5/directives → iteration-6/guards}/admin/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-5/directives → iteration-6/guards}/admin/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-5/directives → iteration-6/guards}/admin/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-5/directives → iteration-6/guards}/admin/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 '../../shared/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-5/directives → iteration-6/guards}/admin/shared/book-validators.ts RENAMED
@@ -0,0 +1,30 @@
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
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
+ static atLeastOneAuthor(controlArray: AbstractControl): ValidationErrors | null {
21
+ if ((controlArray as FormArray).controls.some((el: AbstractControl) => el.value)) {
22
+ return null;
23
+ } else {
24
+ return {
25
+ atLeastOneAuthor: { valid: false }
26
+ };
27
+ }
28
+ }
29
+  
30
+ }
tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/app-routing.module.ts RENAMED
@@ -2,10 +2,7 @@
2
  import { Routes, RouterModule } from '@angular/router';
3
   
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
  {
@@ -19,24 +16,12 @@
19
  },
20
  {
21
  path: 'books',
22
- component: BookListComponent
23
- },
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
   
2
  import { Routes, RouterModule } from '@angular/router';
3
   
4
  import { HomeComponent } from './home/home.component';
5
+ import { CanNavigateToAdminGuard } from './can-navigate-to-admin.guard';
   
   
   
6
   
7
  export const routes: Routes = [
8
  {
16
  },
17
  {
18
  path: 'books',
19
+ loadChildren: () => import('src/app/book-monkey/iteration-6/guards/books/books.module').then(m => m.BooksModule)
   
   
   
   
20
  },
21
  {
22
  path: 'admin',
23
+ loadChildren: () => import('src/app/book-monkey/iteration-6/guards/admin/admin.module').then(m => m.AdminModule),
24
+ canActivate: [CanNavigateToAdminGuard]
   
   
   
   
   
   
   
   
25
  }
26
  ];
27
   
tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/app.module.ts RENAMED
@@ -1,49 +1,25 @@
1
  import { CommonModule } from '@angular/common';
2
  import { NgModule, LOCALE_ID } 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
  import localeDe from '@angular/common/locales/de';
7
  import { registerLocaleData } from '@angular/common';
8
   
9
  import { AppRoutingModule } from './app-routing.module.one-app';
10
  import { AppComponent } from './app.component';
11
  import { HomeComponent } from './home/home.component';
12
- import { BookListComponent } from './book-list/book-list.component';
13
- import { BookListItemComponent } from './book-list-item/book-list-item.component';
14
- import { BookDetailsComponent } from './book-details/book-details.component';
15
  import { SearchComponent } from './search/search.component';
16
  import { TokenInterceptor } from './shared/token.interceptor';
17
- import { BookFormComponent } from './book-form/book-form.component';
18
- import { CreateBookComponent } from './create-book/create-book.component';
19
- import { FormMessagesComponent } from './form-messages/form-messages.component';
20
- import { EditBookComponent } from './edit-book/edit-book.component';
21
- import { IsbnPipe } from './shared/isbn.pipe';
22
- import { ZoomDirective } from './shared/zoom.directive';
23
- import { DelayDirective } from './shared/delay.directive';
24
   
25
  @NgModule({
26
  declarations: [
27
  AppComponent,
28
  HomeComponent,
29
- BookListComponent,
30
- BookListItemComponent,
31
- BookDetailsComponent,
32
- SearchComponent,
33
- BookFormComponent,
34
- CreateBookComponent,
35
- FormMessagesComponent,
36
- EditBookComponent,
37
- IsbnPipe,
38
- ZoomDirective,
39
- DelayDirective
40
  ],
41
  imports: [
42
  CommonModule,
43
  HttpClientModule,
44
- AppRoutingModule,
45
- ReactiveFormsModule,
46
- DateValueAccessorModule
47
  ],
48
  providers: [
49
  { provide: HTTP_INTERCEPTORS, useClass: TokenInterceptor, multi: true },
1
  import { CommonModule } from '@angular/common';
2
  import { NgModule, LOCALE_ID } from '@angular/core';
3
  import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
   
   
4
  import localeDe from '@angular/common/locales/de';
5
  import { registerLocaleData } from '@angular/common';
6
   
7
  import { AppRoutingModule } from './app-routing.module.one-app';
8
  import { AppComponent } from './app.component';
9
  import { HomeComponent } from './home/home.component';
   
   
   
10
  import { SearchComponent } from './search/search.component';
11
  import { TokenInterceptor } from './shared/token.interceptor';
   
   
   
   
   
   
   
12
   
13
  @NgModule({
14
  declarations: [
15
  AppComponent,
16
  HomeComponent,
17
+ SearchComponent
   
   
   
   
   
   
   
   
   
   
18
  ],
19
  imports: [
20
  CommonModule,
21
  HttpClientModule,
22
+ AppRoutingModule
   
   
23
  ],
24
  providers: [
25
  { provide: HTTP_INTERCEPTORS, useClass: TokenInterceptor, multi: true },
tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/books/book-details/book-details.component.html RENAMED
@@ -0,0 +1,48 @@
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
1
+ <div *ngIf="book; else loading">
2
+ <h1>{{ book.title }}</h1>
3
+ <h3 *ngIf="book.subtitle">{{ book.subtitle }}</h3>
4
+ <div class="ui divider"></div>
5
+ <div class="ui grid">
6
+ <div class="four wide column">
7
+ <h4>Autoren</h4>
8
+ <ng-container *ngFor="let author of book.authors">
9
+ {{ author }}<br>
10
+ </ng-container>
11
+ </div>
12
+ <div class="four wide column">
13
+ <h4>ISBN</h4>
14
+ {{ book.isbn | isbn }}
15
+ </div>
16
+ <div class="four wide column">
17
+ <h4>Erschienen</h4>
18
+ {{ book.published | date:'longDate' }}
19
+ </div>
20
+ <div class="four wide column" *ngIf="book.rating">
21
+ <h4>Rating</h4>
22
+ <ng-container
23
+ *ngFor="let r of getRating(book.rating);
24
+ index as i">
25
+ <i class="yellow star icon"
26
+ *bmDelay="500 + i * 200"></i>
27
+ </ng-container>
28
+ </div>
29
+ </div>
30
+ <h4>Beschreibung</h4>
31
+ <p>{{ book.description }}</p>
32
+ <div class="ui small images">
33
+ <img *ngFor="let thumbnail of book.thumbnails"
34
+ [src]="thumbnail.url">
35
+ </div>
36
+ <button class="ui tiny red labeled icon button"
37
+ (click)="removeBook()">
38
+ <i class="remove icon"></i> Buch löschen
39
+ </button>
40
+ <a class="ui tiny yellow labeled icon button"
41
+ [routerLink]="['../../admin/edit', book.isbn]">
42
+ <i class="write icon"></i> Buch bearbeiten
43
+ </a>
44
+ </div>
45
+  
46
+ <ng-template #loading>
47
+ <div class="ui active centered inline loader"></div>
48
+ </ng-template>
tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/books/book-details/book-details.component.ts RENAMED
@@ -0,0 +1,37 @@
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
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-book-details',
9
+ templateUrl: './book-details.component.html',
10
+ styleUrls: ['./book-details.component.css']
11
+ })
12
+ export class BookDetailsComponent implements OnInit {
13
+ book?: Book;
14
+  
15
+ constructor(
16
+ private bs: BookStoreService,
17
+ private router: Router,
18
+ private route: ActivatedRoute
19
+ ) { }
20
+  
21
+ ngOnInit(): void {
22
+ const params = this.route.snapshot.paramMap;
23
+ this.bs.getSingle(params.get('isbn') || '')
24
+ .subscribe(b => this.book = b);
25
+ }
26
+  
27
+ getRating(num: number) {
28
+ return new Array(num);
29
+ }
30
+  
31
+ removeBook() {
32
+ if (this.book && confirm('Buch wirklich löschen?')) {
33
+ this.bs.remove(this.book.isbn)
34
+ .subscribe(res => this.router.navigate(['../'], { relativeTo: this.route }));
35
+ }
36
+ }
37
+ }
tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/books/book-list/book-list.component.html RENAMED
@@ -0,0 +1,19 @@
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
1
+ <div class="ui middle aligned selection divided list">
2
+
3
+ <ng-container *ngIf="books$ | async as books; else loading">
4
+ <bm-book-list-item class="item"
5
+ *ngFor="let b of books"
6
+ [book]="b"
7
+ [routerLink]="b.isbn"></bm-book-list-item>
8
+
9
+ <p *ngIf="!books.length">Es wurden noch keine Bücher eingetragen.</p>
10
+ </ng-container>
11
+  
12
+ <ng-template #loading>
13
+ <div class="ui active dimmer">
14
+ <div class="ui large text loader">Daten werden geladen...</div>
15
+ </div>
16
+ </ng-template>
17
+  
18
+ </div>
19
+
tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/books/book-list/book-list.component.ts RENAMED
@@ -0,0 +1,20 @@
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
1
+ import { Component, OnInit } from '@angular/core';
2
+ import { Observable } from 'rxjs';
3
+  
4
+ import { Book } from '../../shared/book';
5
+ import { BookStoreService } from '../../shared/book-store.service';
6
+  
7
+ @Component({
8
+ selector: 'bm-book-list',
9
+ templateUrl: './book-list.component.html',
10
+ styleUrls: ['./book-list.component.css']
11
+ })
12
+ export class BookListComponent implements OnInit {
13
+ books$: Observable<Book[]>;
14
+  
15
+ constructor(private bs: BookStoreService) {
16
+ this.books$ = this.bs.getAll();
17
+ }
18
+  
19
+ ngOnInit(): void {}
20
+ }
tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/books/book-list-item/book-list-item.component.html RENAMED
@@ -0,0 +1,17 @@
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
1
+ <ng-container *ngIf="book">
2
+ <img class="ui tiny image"
3
+ *ngIf="book.thumbnails && book.thumbnails[0] && book.thumbnails[0].url"
4
+ [src]="book.thumbnails[0].url"
5
+ bmZoom>
6
+ <div class="content">
7
+ <div class="header">{{ book.title }}</div>
8
+ <div *ngIf="book.subtitle" class="description">{{ book.subtitle }}</div>
9
+ <div class="metadata">
10
+ <span *ngFor="let author of book.authors; last as l">
11
+ {{ author }}<span *ngIf="!l">, </span>
12
+ </span>
13
+ <br>
14
+ ISBN {{ book.isbn | isbn }}
15
+ </div>
16
+ </div>
17
+ </ng-container>
tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/books/book-list-item/book-list-item.component.ts RENAMED
@@ -0,0 +1,15 @@
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
1
+ import { Component, OnInit, Input } from '@angular/core';
2
+  
3
+ import { Book } from '../../shared/book';
4
+  
5
+ @Component({
6
+ selector: 'bm-book-list-item',
7
+ templateUrl: './book-list-item.component.html',
8
+ styleUrls: ['./book-list-item.component.css']
9
+ })
10
+ export class BookListItemComponent implements OnInit {
11
+ @Input() book?: Book;
12
+  
13
+ ngOnInit(): void {
14
+ }
15
+ }
tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/books/books-routing.module.ts RENAMED
@@ -0,0 +1,23 @@
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
1
+ import { NgModule } from '@angular/core';
2
+ import { Routes, RouterModule } from '@angular/router';
3
+  
4
+ import { BookListComponent } from './book-list/book-list.component';
5
+ import { BookDetailsComponent } from './book-details/book-details.component';
6
+  
7
+ const routes: Routes = [
8
+ {
9
+ path: '',
10
+ component: BookListComponent
11
+ },
12
+ {
13
+ path: ':isbn',
14
+ component: BookDetailsComponent
15
+ }
16
+ ];
17
+  
18
+ @NgModule({
19
+ imports: [RouterModule.forChild(routes)],
20
+ exports: [RouterModule],
21
+ providers: []
22
+ })
23
+ export class BooksRoutingModule { }
tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/books/books.module.ts RENAMED
@@ -0,0 +1,26 @@
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
1
+ import { DelayDirective } from './shared/delay.directive';
2
+ import { NgModule } from '@angular/core';
3
+ import { CommonModule } from '@angular/common';
4
+ import { BooksRoutingModule } from './books-routing.module';
5
+  
6
+ import { BookListComponent } from './book-list/book-list.component';
7
+ import { BookListItemComponent } from './book-list-item/book-list-item.component';
8
+ import { BookDetailsComponent } from './book-details/book-details.component';
9
+ import { IsbnPipe } from './shared/isbn.pipe';
10
+ import { ZoomDirective } from './shared/zoom.directive';
11
+  
12
+ @NgModule({
13
+ imports: [
14
+ CommonModule,
15
+ BooksRoutingModule
16
+ ],
17
+ declarations: [
18
+ BookListComponent,
19
+ BookListItemComponent,
20
+ BookDetailsComponent,
21
+ IsbnPipe,
22
+ ZoomDirective,
23
+ DelayDirective
24
+ ]
25
+ })
26
+ export class BooksModule { }
tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/books/shared/delay.directive.ts RENAMED
@@ -0,0 +1,20 @@
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
1
+ import { Directive, OnInit, Input, TemplateRef, ViewContainerRef } from '@angular/core';
2
+  
3
+ @Directive({
4
+ selector: '[bmDelay]'
5
+ })
6
+ export class DelayDirective implements OnInit {
7
+ @Input() bmDelay: number = 100;
8
+  
9
+ constructor(
10
+ private templateRef: TemplateRef<any>,
11
+ private viewContainerRef: ViewContainerRef
12
+ ) { }
13
+  
14
+ ngOnInit(): void {
15
+ setTimeout(() => {
16
+ this.viewContainerRef.createEmbeddedView(this.templateRef);
17
+ }, this.bmDelay);
18
+ }
19
+  
20
+ }
tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/books/shared/isbn.pipe.ts RENAMED
@@ -0,0 +1,12 @@
   
   
   
   
   
   
   
   
   
   
   
   
1
+ import { Pipe, PipeTransform } from '@angular/core';
2
+  
3
+ @Pipe({
4
+ name: 'isbn'
5
+ })
6
+ export class IsbnPipe implements PipeTransform {
7
+  
8
+ transform(value: string | null): string {
9
+ if (!value) { return ''; }
10
+ return `${value.substr(0, 3)}-${value.substr(3)}`;
11
+ }
12
+ }
tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/books/shared/zoom.directive.ts RENAMED
@@ -0,0 +1,15 @@
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
1
+ import { Directive, HostBinding, HostListener } from '@angular/core';
2
+  
3
+ @Directive({
4
+ selector: '[bmZoom]'
5
+ })
6
+ export class ZoomDirective {
7
+ @HostBinding('class.small') isZoomed: boolean = false;
8
+  
9
+ @HostListener('mouseenter') onMouseEnter() {
10
+ this.isZoomed = true;
11
+ }
12
+ @HostListener('mouseleave') onMouseLeave() {
13
+ this.isZoomed = false;
14
+ }
15
+ }
tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/guards}/can-navigate-to-admin.guard.ts RENAMED
@@ -0,0 +1,17 @@
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
1
+ import { Injectable } from '@angular/core';
2
+ import { CanActivate } from '@angular/router';
3
+  
4
+ @Injectable({
5
+ providedIn: 'root'
6
+ })
7
+ export class CanNavigateToAdminGuard implements CanActivate {
8
+
9
+ accessGranted = false;
10
+  
11
+ canActivate(): boolean {
12
+ if (!this.accessGranted) {
13
+ this.accessGranted = window.confirm('Mit großer Macht kommt große Verantwortung. Möchten Sie den Admin-Bereich betreten?');
14
+ }
15
+ return this.accessGranted;
16
+ }
17
+ }