BookMonkey 3 Diff

Files changed (12) hide show
  1. tmp/src/app/book-monkey/{iteration-2/routing → iteration-3/interceptors}/app.module.ts +9 -2
  2. tmp/src/app/book-monkey/{iteration-2/routing → iteration-3/interceptors}/book-details/book-details.component.html +9 -1
  3. tmp/src/app/book-monkey/{iteration-2/routing → iteration-3/interceptors}/book-details/book-details.component.ts +11 -2
  4. tmp/src/app/book-monkey/{iteration-2/routing → iteration-3/interceptors}/book-list/book-list.component.html +6 -0
  5. tmp/src/app/book-monkey/{iteration-2/routing → iteration-3/interceptors}/book-list/book-list.component.ts +1 -1
  6. tmp/src/app/book-monkey/{iteration-2/routing → iteration-3/interceptors}/home/home.component.html +3 -0
  7. tmp/src/app/book-monkey/{iteration-2/routing → iteration-3/interceptors}/search/search.component.html +17 -0
  8. tmp/src/app/book-monkey/{iteration-2/routing → iteration-3/interceptors}/search/search.component.ts +32 -0
  9. tmp/src/app/book-monkey/{iteration-2/routing → iteration-3/interceptors}/shared/book-factory.ts +11 -0
  10. tmp/src/app/book-monkey/{iteration-2/routing → iteration-3/interceptors}/shared/book-raw.ts +15 -0
  11. tmp/src/app/book-monkey/{iteration-2/routing → iteration-3/interceptors}/shared/book-store.service.ts +49 -34
  12. tmp/src/app/book-monkey/{iteration-2/routing → iteration-3/interceptors}/shared/token-interceptor.ts +20 -0
tmp/src/app/book-monkey/{iteration-2/routing → iteration-3/interceptors}/app.module.ts RENAMED
@@ -1,5 +1,6 @@
1
import { CommonModule } from '@angular/common';
2
import { NgModule } from '@angular/core';
3
4
import { AppRoutingModule } from './app-routing.module.one-app';
5
import { AppComponent } from './app.component';
@@ -7,6 +8,8 @@
7
import { BookListComponent } from './book-list/book-list.component';
8
import { BookListItemComponent } from './book-list-item/book-list-item.component';
9
import { BookDetailsComponent } from './book-details/book-details.component';
10
11
@NgModule({
12
declarations: [
@@ -14,13 +17,17 @@
14
HomeComponent,
15
BookListComponent,
16
BookListItemComponent,
17
- BookDetailsComponent
18
],
19
imports: [
20
CommonModule,
21
AppRoutingModule
22
],
23
- providers: [],
24
bootstrap: [AppComponent]
25
})
26
export class AppModule { }
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';
8
import { BookListComponent } from './book-list/book-list.component';
9
import { BookListItemComponent } from './book-list-item/book-list-item.component';
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: [
17
HomeComponent,
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 }
30
+ ],
31
bootstrap: [AppComponent]
32
})
33
export class AppModule { }
tmp/src/app/book-monkey/{iteration-2/routing → iteration-3/interceptors}/book-details/book-details.component.html RENAMED
@@ -1,4 +1,4 @@
1
- <div *ngIf="book">
2
<h1>{{ book.title }}</h1>
3
<h3 *ngIf="book.subtitle">{{ book.subtitle }}</h3>
4
<div class="ui divider"></div>
@@ -29,4 +29,12 @@
29
<img *ngFor="let thumbnail of book.thumbnails"
30
[src]="thumbnail.url">
31
</div>
32
</div>
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>
29
<img *ngFor="let thumbnail of book.thumbnails"
30
[src]="thumbnail.url">
31
</div>
32
+ <button class="ui tiny red labeled icon button"
33
+ (click)="removeBook()">
34
+ <i class="remove icon"></i> Buch löschen
35
+ </button>
36
</div>
37
+
38
+ <ng-template #loading>
39
+ <div class="ui active centered inline loader"></div>
40
+ </ng-template>
tmp/src/app/book-monkey/{iteration-2/routing → iteration-3/interceptors}/book-details/book-details.component.ts RENAMED
@@ -1,5 +1,5 @@
1
import { Component, OnInit } from '@angular/core';
2
- import { ActivatedRoute } from '@angular/router';
3
4
import { Book } from '../shared/book';
5
import { BookStoreService } from '../shared/book-store.service';
@@ -14,15 +14,24 @@
14
15
constructor(
16
private bs: BookStoreService,
17
private route: ActivatedRoute
18
) { }
19
20
ngOnInit() {
21
const params = this.route.snapshot.paramMap;
22
- this.book = this.bs.getSingle(params.get('isbn'));
23
}
24
25
getRating(num: number) {
26
return new Array(num);
27
}
28
}
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';
14
15
constructor(
16
private bs: BookStoreService,
17
+ private router: Router,
18
private route: ActivatedRoute
19
) { }
20
21
ngOnInit() {
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 (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-2/routing → iteration-3/interceptors}/book-list/book-list.component.html RENAMED
@@ -3,4 +3,10 @@
3
*ngFor="let b of books"
4
[book]="b"
5
[routerLink]="b.isbn"></bm-book-list-item>
6
</div>
3
*ngFor="let b of books"
4
[book]="b"
5
[routerLink]="b.isbn"></bm-book-list-item>
6
+
7
+ <div *ngIf="!books" class="ui active dimmer">
8
+ <div class="ui large text loader">Daten werden geladen...</div>
9
+ </div>
10
+
11
+ <p *ngIf="books && !books.length">Es wurden noch keine Bücher eingetragen.</p>
12
</div>
tmp/src/app/book-monkey/{iteration-2/routing → iteration-3/interceptors}/book-list/book-list.component.ts RENAMED
@@ -14,6 +14,6 @@
14
constructor(private bs: BookStoreService) { }
15
16
ngOnInit() {
17
- this.books = this.bs.getAll();
18
}
19
}
14
constructor(private bs: BookStoreService) { }
15
16
ngOnInit() {
17
+ this.bs.getAll().subscribe(res => this.books = res);
18
}
19
}
tmp/src/app/book-monkey/{iteration-2/routing → iteration-3/interceptors}/home/home.component.html RENAMED
@@ -4,3 +4,6 @@
4
Buchliste ansehen
5
<i class="right arrow icon"></i>
6
</a>
4
Buchliste ansehen
5
<i class="right arrow icon"></i>
6
</a>
7
+
8
+ <h2>Suche</h2>
9
+ <bm-search></bm-search>
tmp/src/app/book-monkey/{iteration-2/routing → iteration-3/interceptors}/search/search.component.html RENAMED
@@ -0,0 +1,17 @@
1
+ <div class="ui search" [class.loading]="isLoading">
2
+ <div class="ui icon input">
3
+ <input type="text" class="prompt"
4
+ (keyup)="keyUp$.next($event.target.value)">
5
+ <i class="search icon"></i>
6
+ </div>
7
+
8
+ <div class="results transition visible"
9
+ *ngIf="foundBooks.length">
10
+ <a class="result"
11
+ *ngFor="let book of foundBooks"
12
+ [routerLink]="['..', 'books', book.isbn]">
13
+ {{ book.title }}
14
+ <p class="description">{{ book.subtitle }}</p>
15
+ </a>
16
+ </div>
17
+ </div>
tmp/src/app/book-monkey/{iteration-2/routing → iteration-3/interceptors}/search/search.component.ts RENAMED
@@ -0,0 +1,32 @@
1
+ import { Component, OnInit } from '@angular/core';
2
+ import { Subject } from 'rxjs';
3
+ import { debounceTime, distinctUntilChanged, tap, switchMap, filter } from 'rxjs/operators';
4
+
5
+ import { Book } from '../shared/book';
6
+ import { BookStoreService } from '../shared/book-store.service';
7
+
8
+ @Component({
9
+ selector: 'bm-search',
10
+ templateUrl: './search.component.html',
11
+ styleUrls: ['./search.component.css']
12
+ })
13
+ export class SearchComponent implements OnInit {
14
+
15
+ keyUp$ = new Subject<string>();
16
+ isLoading = false;
17
+ foundBooks: Book[] = [];
18
+
19
+ constructor(private bs: BookStoreService) { }
20
+
21
+ ngOnInit() {
22
+ this.keyUp$.pipe(
23
+ filter(term => term.length >= 3),
24
+ debounceTime(500),
25
+ distinctUntilChanged(),
26
+ tap(() => this.isLoading = true),
27
+ switchMap(searchTerm => this.bs.getAllSearch(searchTerm)),
28
+ tap(() => this.isLoading = false)
29
+ )
30
+ .subscribe(books => this.foundBooks = books);
31
+ }
32
+ }
tmp/src/app/book-monkey/{iteration-2/routing → iteration-3/interceptors}/shared/book-factory.ts RENAMED
@@ -0,0 +1,11 @@
1
+ import { Book } from './book';
2
+ import { BookRaw } from './book-raw';
3
+
4
+ export class BookFactory {
5
+ static fromRaw(b: BookRaw): Book {
6
+ return {
7
+ ...b,
8
+ published: new Date(b.published)
9
+ };
10
+ }
11
+ }
tmp/src/app/book-monkey/{iteration-2/routing → iteration-3/interceptors}/shared/book-raw.ts RENAMED
@@ -0,0 +1,15 @@
1
+ export interface BookRaw {
2
+ isbn: string;
3
+ title: string;
4
+ authors: string[];
5
+ published: string;
6
+ subtitle?: string;
7
+ rating?: number;
8
+ thumbnails?: ThumbnailRaw[];
9
+ description?: string;
10
+ }
11
+
12
+ export interface ThumbnailRaw {
13
+ url: string;
14
+ title?: string;
15
+ }
tmp/src/app/book-monkey/{iteration-2/routing → iteration-3/interceptors}/shared/book-store.service.ts RENAMED
@@ -1,49 +1,64 @@
1
import { Injectable } from '@angular/core';
2
3
import { Book } from './book';
4
5
@Injectable({
6
providedIn: 'root'
7
})
8
export class BookStoreService {
9
- books: Book[];
10
11
- constructor() {
12
- this.books = [
13
- {
14
- isbn: '9783864906466',
15
- title: 'Angular',
16
- authors: ['Ferdinand Malcher', 'Johannes Hoppe', 'Danny Koppenhagen'],
17
- published: new Date(2019, 4, 30),
18
- subtitle: 'Grundlagen, fortgeschrittene Themen und Best Practices - mit NativeScript und NgRx',
19
- rating: 5,
20
- thumbnails: [{
21
- url: 'https://ng-buch.de/buch1.jpg',
22
- title: 'Buchcover'
23
- }],
24
- description: 'Die Autoren führen Sie mit einem anspruchsvollen Beispielprojekt durch die Welt von Angular...'
25
- },
26
- {
27
- isbn: '9783864903274',
28
- title: 'React',
29
- authors: ['Oliver Zeigermann', 'Nils Hartmann'],
30
- published: new Date(2016, 6, 17),
31
- subtitle: 'Die praktische Einführung in React, React Router und Redux',
32
- rating: 3,
33
- thumbnails: [{
34
- url: 'https://ng-buch.de/buch2.jpg',
35
- title: 'Buchcover'
36
- }],
37
- description: 'React ist ein JavaScript-Framework zur Entwicklung von Benutzeroberflächen...'
38
- }
39
- ];
40
}
41
42
- getAll(): Book[] {
43
- return this.books;
44
}
45
46
- getSingle(isbn: string): Book {
47
- return this.books.find(book => book.isbn === isbn);
48
}
49
}
1
import { Injectable } from '@angular/core';
2
+ import { HttpClient, HttpErrorResponse } from '@angular/common/http';
3
+ import { throwError, Observable } from 'rxjs';
4
+ import { retry, map, catchError } from 'rxjs/operators';
5
6
import { Book } from './book';
7
+ import { BookRaw } from './book-raw';
8
+ import { BookFactory } from './book-factory';
9
10
@Injectable({
11
providedIn: 'root'
12
})
13
export class BookStoreService {
14
+ private api = 'https://api3.angular-buch.com/secure';
15
16
+ constructor(private http: HttpClient) {}
17
+
18
+ getAll(): Observable<Book[]> {
19
+ return this.http.get<BookRaw[]>(`${this.api}/books`)
20
+ .pipe(
21
+ retry(3),
22
+ map(booksRaw =>
23
+ booksRaw.map(b => BookFactory.fromRaw(b)),
24
+ ),
25
+ catchError(this.errorHandler)
26
+ );
27
+ }
28
+
29
+ getSingle(isbn: string): Observable<Book> {
30
+ return this.http.get<BookRaw>(
31
+ `${this.api}/book/${isbn}`
32
+ ).pipe(
33
+ retry(3),
34
+ map(b => BookFactory.fromRaw(b)),
35
+ catchError(this.errorHandler)
36
+ );
37
+ }
38
+
39
+ remove(isbn: string): Observable<any> {
40
+ return this.http.delete(
41
+ `${this.api}/book/${isbn}`,
42
+ { responseType: 'text' }
43
+ ).pipe(
44
+ catchError(this.errorHandler)
45
+ );
46
}
47
48
+ getAllSearch(searchTerm: string): Observable<Book[]> {
49
+ return this.http.get<BookRaw[]>(
50
+ `${this.api}/books/search/${searchTerm}`
51
+ ).pipe(
52
+ retry(3),
53
+ map(booksRaw =>
54
+ booksRaw.map(b => BookFactory.fromRaw(b)),
55
+ ),
56
+ catchError(this.errorHandler)
57
+ );
58
}
59
60
+ private errorHandler(error: HttpErrorResponse): Observable<any> {
61
+ console.error('Fehler aufgetreten!');
62
+ return throwError(error);
63
}
64
}
tmp/src/app/book-monkey/{iteration-2/routing → iteration-3/interceptors}/shared/token-interceptor.ts RENAMED
@@ -0,0 +1,20 @@
1
+ import { Injectable } from '@angular/core';
2
+ import { HttpInterceptor, HttpHandler, HttpRequest, HttpEvent } from '@angular/common/http';
3
+ import { Observable } from 'rxjs';
4
+
5
+ @Injectable()
6
+ export class TokenInterceptor implements HttpInterceptor {
7
+ private authToken = '1234567890';
8
+
9
+ intercept(
10
+ request: HttpRequest<any>,
11
+ next: HttpHandler
12
+ ): Observable<HttpEvent<any>> {
13
+ const newRequest = request.clone({
14
+ setHeaders: {
15
+ Authorization: `Bearer ${this.authToken}`
16
+ }
17
+ });
18
+ return next.handle(newRequest);
19
+ }
20
+ }