| @@ -8,6 +8,7 @@ | |
| 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 |  | 
| 12 | 
             
            @NgModule({
         | 
| 13 | 
             
              declarations: [
         | 
| @@ -15,7 +16,8 @@ | |
| 15 | 
             
                HomeComponent,
         | 
| 16 | 
             
                BookListComponent,
         | 
| 17 | 
             
                BookListItemComponent,
         | 
| 18 | 
            -
                BookDetailsComponent
         | 
|  | |
| 19 | 
             
              ],
         | 
| 20 | 
             
              imports: [
         | 
| 21 | 
             
                CommonModule,
         | 
| 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 |  | 
| 13 | 
             
            @NgModule({
         | 
| 14 | 
             
              declarations: [
         | 
| 16 | 
             
                HomeComponent,
         | 
| 17 | 
             
                BookListComponent,
         | 
| 18 | 
             
                BookListItemComponent,
         | 
| 19 | 
            +
                BookDetailsComponent,
         | 
| 20 | 
            +
                SearchComponent
         | 
| 21 | 
             
              ],
         | 
| 22 | 
             
              imports: [
         | 
| 23 | 
             
                CommonModule,
         | 
| @@ -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>
         | 
| @@ -0,0 +1,18 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
| 1 | 
            +
            <div class="ui search" [class.loading]="isLoading">
         | 
| 2 | 
            +
              <div class="ui icon input">
         | 
| 3 | 
            +
                
         | 
| 4 | 
            +
                <input type="text" class="prompt" #input
         | 
| 5 | 
            +
                  (keyup)="keyUp$.next(input.value)">
         | 
| 6 | 
            +
                <i class="search icon"></i>
         | 
| 7 | 
            +
              </div>
         | 
| 8 | 
            +
             
         | 
| 9 | 
            +
              <div class="results transition visible"
         | 
| 10 | 
            +
                *ngIf="foundBooks.length">
         | 
| 11 | 
            +
                <a class="result"
         | 
| 12 | 
            +
                  *ngFor="let book of foundBooks"
         | 
| 13 | 
            +
                  [routerLink]="['..', 'books', book.isbn]">
         | 
| 14 | 
            +
                  {{ book.title }}
         | 
| 15 | 
            +
                  <p class="description">{{ book.subtitle }}</p>
         | 
| 16 | 
            +
                </a>
         | 
| 17 | 
            +
              </div>
         | 
| 18 | 
            +
            </div>
         | 
| @@ -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(): void {
         | 
| 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 | 
            +
            }
         | 
| @@ -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 | 
            +
            }
         | 
| @@ -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 | 
            +
            }
         | 
| @@ -1,8 +1,11 @@ | |
| 1 | 
             
            import { Injectable } from '@angular/core';
         | 
| 2 | 
            -
            import { HttpClient } from '@angular/common/http';
         | 
| 3 | 
            -
            import { Observable } from 'rxjs';
         | 
|  | |
| 4 |  | 
| 5 | 
             
            import { Book } from './book';
         | 
|  | |
|  | |
| 6 |  | 
| 7 | 
             
            @Injectable({
         | 
| 8 | 
             
              providedIn: 'root'
         | 
| @@ -13,12 +16,23 @@ | |
| 13 | 
             
              constructor(private http: HttpClient) {}
         | 
| 14 |  | 
| 15 | 
             
              getAll(): Observable<Book[]> {
         | 
| 16 | 
            -
                return this.http.get< | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 17 | 
             
              }
         | 
| 18 |  | 
| 19 | 
             
              getSingle(isbn: string): Observable<Book> {
         | 
| 20 | 
            -
                return this.http.get< | 
| 21 | 
             
                  `${this.api}/book/${isbn}`
         | 
|  | |
|  | |
|  | |
|  | |
| 22 | 
             
                );
         | 
| 23 | 
             
              }
         | 
| 24 |  | 
| @@ -26,6 +40,25 @@ | |
| 26 | 
             
                return this.http.delete(
         | 
| 27 | 
             
                  `${this.api}/book/${isbn}`,
         | 
| 28 | 
             
                  { responseType: 'text' }
         | 
|  | |
|  | |
| 29 | 
             
                );
         | 
| 30 | 
             
              }
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 31 | 
             
            }
         | 
| 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'
         | 
| 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 |  | 
| 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 | 
             
            }
         |