Differenzansicht 07-http
im Vergleich zu 06-routing

← Zurück zur Übersicht | Demo | Quelltext auf GitHub
src/app/app.module.ts CHANGED
@@ -1,4 +1,5 @@
1
  import { NgModule } from '@angular/core';
 
2
  import { BrowserModule } from '@angular/platform-browser';
3
 
4
  import { AppRoutingModule } from './app-routing.module';
@@ -14,6 +15,7 @@ import { HomeComponent } from './home/home.component';
14
  imports: [
15
  BrowserModule,
16
  AppRoutingModule,
 
17
  BooksModule
18
  ],
19
  providers: [],
1
  import { NgModule } from '@angular/core';
2
+ import { HttpClientModule } from '@angular/common/http';
3
  import { BrowserModule } from '@angular/platform-browser';
4
 
5
  import { AppRoutingModule } from './app-routing.module';
15
  imports: [
16
  BrowserModule,
17
  AppRoutingModule,
18
+ HttpClientModule,
19
  BooksModule
20
  ],
21
  providers: [],
src/app/books/book-details/book-details.component.html CHANGED
@@ -23,4 +23,7 @@
23
  [src]="book.thumbnailUrl"
24
  alt="Cover">
25
  <a class="button arrow-left" routerLink="..">Back to list</a>
 
 
 
26
  </div>
23
  [src]="book.thumbnailUrl"
24
  alt="Cover">
25
  <a class="button arrow-left" routerLink="..">Back to list</a>
26
+ <button class="red" (click)="removeBook(book.isbn)">
27
+ Remove book
28
+ </button>
29
  </div>
src/app/books/book-details/book-details.component.ts CHANGED
@@ -1,5 +1,5 @@
1
  import { Component } from '@angular/core';
2
- import { ActivatedRoute } from '@angular/router';
3
 
4
  import { BookStoreService } from '../../shared/book-store.service';
5
  import { Book } from '../../shared/book';
@@ -14,8 +14,19 @@ export class BookDetailsComponent {
14
  constructor(
15
  private service: BookStoreService,
16
  private route: ActivatedRoute,
 
17
  ) {
18
  const isbn = this.route.snapshot.paramMap.get('isbn')!;
19
- this.book = this.service.getSingle(isbn);
 
 
 
 
 
 
 
 
 
 
20
  }
21
  }
1
  import { Component } from '@angular/core';
2
+ import { ActivatedRoute, Router } from '@angular/router';
3
 
4
  import { BookStoreService } from '../../shared/book-store.service';
5
  import { Book } from '../../shared/book';
14
  constructor(
15
  private service: BookStoreService,
16
  private route: ActivatedRoute,
17
+ private router: Router
18
  ) {
19
  const isbn = this.route.snapshot.paramMap.get('isbn')!;
20
+ this.service.getSingle(isbn).subscribe(book => {
21
+ this.book = book;
22
+ });
23
+ }
24
+
25
+ removeBook(isbn: string) {
26
+ if (window.confirm('Remove book?')) {
27
+ this.service.remove(isbn).subscribe(() => {
28
+ this.router.navigateByUrl('/books');
29
+ });
30
+ }
31
  }
32
  }
src/app/books/book-list/book-list.component.spec.ts DELETED
@@ -1,23 +0,0 @@
1
- import { ComponentFixture, TestBed } from '@angular/core/testing';
2
-
3
- import { BookListComponent } from './book-list.component';
4
-
5
- describe('BookListComponent', () => {
6
- let component: BookListComponent;
7
- let fixture: ComponentFixture<BookListComponent>;
8
-
9
- beforeEach(async () => {
10
- await TestBed.configureTestingModule({
11
- declarations: [ BookListComponent ]
12
- })
13
- .compileComponents();
14
-
15
- fixture = TestBed.createComponent(BookListComponent);
16
- component = fixture.componentInstance;
17
- fixture.detectChanges();
18
- });
19
-
20
- it('should create', () => {
21
- expect(component).toBeTruthy();
22
- });
23
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/books/book-list/book-list.component.ts CHANGED
@@ -12,6 +12,8 @@ export class BookListComponent {
12
  books: Book[] = [];
13
 
14
  constructor(private service: BookStoreService) {
15
- this.books = this.service.getAll();
 
 
16
  }
17
  }
12
  books: Book[] = [];
13
 
14
  constructor(private service: BookStoreService) {
15
+ this.service.getAll().subscribe(books => {
16
+ this.books = books;
17
+ });
18
  }
19
  }
src/app/books/book-list-item/book-list-item.component.spec.ts CHANGED
@@ -1,42 +1,23 @@
1
- import { Location } from '@angular/common';
2
  import { ComponentFixture, TestBed } from '@angular/core/testing';
3
- import { RouterTestingModule } from '@angular/router/testing';
4
 
5
  import { BookListItemComponent } from './book-list-item.component';
6
 
7
  describe('BookListItemComponent', () => {
8
  let component: BookListItemComponent;
9
  let fixture: ComponentFixture<BookListItemComponent>;
10
- let location: Location;
11
 
12
  beforeEach(async () => {
13
  await TestBed.configureTestingModule({
14
- declarations: [BookListItemComponent],
15
- imports: [
16
- RouterTestingModule.withRoutes([
17
- { path: ':isbn', children: [] }
18
- ])
19
- ]
20
  })
21
  .compileComponents();
22
 
23
- location = TestBed.inject(Location);
24
  fixture = TestBed.createComponent(BookListItemComponent);
25
  component = fixture.componentInstance;
26
- // Buch zum Anzeigen
27
- component.book = {
28
- isbn: '111',
29
- title: 'Book 1',
30
- authors: [],
31
- thumbnailUrl: 'https://cdn.ng-buch.de/kochen.png'
32
- };
33
  fixture.detectChanges();
34
  });
35
 
36
- it('should navigate to details page by ISBN', async () => {
37
-
38
- fixture.nativeElement.querySelector('img').click();
39
- await fixture.whenStable();
40
- expect(location.path()).toEqual('/111');
41
  });
42
  });
 
1
  import { ComponentFixture, TestBed } from '@angular/core/testing';
 
2
 
3
  import { BookListItemComponent } from './book-list-item.component';
4
 
5
  describe('BookListItemComponent', () => {
6
  let component: BookListItemComponent;
7
  let fixture: ComponentFixture<BookListItemComponent>;
 
8
 
9
  beforeEach(async () => {
10
  await TestBed.configureTestingModule({
11
+ declarations: [ BookListItemComponent ]
 
 
 
 
 
12
  })
13
  .compileComponents();
14
 
 
15
  fixture = TestBed.createComponent(BookListItemComponent);
16
  component = fixture.componentInstance;
 
 
 
 
 
 
 
17
  fixture.detectChanges();
18
  });
19
 
20
+ it('should create', () => {
21
+ expect(component).toBeTruthy();
 
 
 
22
  });
23
  });
src/app/shared/book-store.service.http-testing-controller.spec.ts ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { TestBed } from '@angular/core/testing';
2
+ import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
3
+
4
+ import { Book } from './book';
5
+ import { BookStoreService } from './book-store.service';
6
+ import { HttpErrorResponse } from '@angular/common/http';
7
+
8
+ describe('BookStoreService', () => {
9
+ let service: BookStoreService;
10
+ let httpMock: HttpTestingController;
11
+ const expectedBooks = [
12
+ { isbn: '111', title: 'Book 1', authors: [] },
13
+ { isbn: '222', title: 'Book 2', authors: [] }
14
+ ];
15
+
16
+ beforeEach(() => {
17
+ TestBed.configureTestingModule({
18
+ imports: [HttpClientTestingModule]
19
+ });
20
+
21
+ service = TestBed.inject(BookStoreService);
22
+ httpMock = TestBed.inject(HttpTestingController);
23
+ });
24
+
25
+ it('should GET a list of all books', () => {
26
+
27
+ let receivedBooks!: Book[];
28
+ service.getAll().subscribe(books => receivedBooks = books);
29
+
30
+ // Request aus der Warteschlange holen
31
+ const req = httpMock.expectOne(
32
+ 'https://api5.angular-buch.com/books');
33
+ expect(req.request.method).toEqual('GET');
34
+
35
+ // flush -- jetzt werden die Bücher emittiert
36
+ req.flush(expectedBooks);
37
+
38
+ expect(receivedBooks).toHaveSize(2);
39
+ expect(receivedBooks[0].isbn).toBe('111');
40
+ expect(receivedBooks[1].isbn).toBe('222');
41
+ });
42
+
43
+ it('should throw a HttpErrorResponse on server errors', () => {
44
+
45
+ let errorResponse!: HttpErrorResponse;
46
+
47
+ service.getAll().subscribe({
48
+ error: (error: any) => errorResponse = error
49
+ });
50
+
51
+ const req = httpMock.expectOne('https://api5.angular-buch.com/books');
52
+
53
+ req.flush('Fehler!', {
54
+ status: 500,
55
+ statusText: 'Internal Server Error'
56
+ });
57
+
58
+ expect(errorResponse.status).toBe(500);
59
+ expect(errorResponse.statusText).toBe('Internal Server Error');
60
+ });
61
+
62
+ afterEach(() => {
63
+ // prüfen, ob kein Request übrig geblieben ist
64
+ httpMock.verify();
65
+ });
66
+ });
src/app/shared/book-store.service.mock.spec.ts ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { HttpClient } from '@angular/common/http';
2
+ import { TestBed } from '@angular/core/testing';
3
+ import { Observable, of } from 'rxjs';
4
+
5
+ import { Book } from '../shared/book';
6
+ import { BookStoreService } from './book-store.service';
7
+
8
+ describe('BookStoreService', () => {
9
+ let service: BookStoreService;
10
+ let httpMock: {
11
+ get: (url: string) => Observable<Book[]>;
12
+ };
13
+
14
+ beforeEach(() => {
15
+
16
+ const expectedBooks: Book[] = [
17
+ { isbn: '111', title: 'Book 1', authors: [] },
18
+ { isbn: '222', title: 'Book 2', authors: [] }
19
+ ];
20
+
21
+ httpMock = {
22
+ get: () => of(expectedBooks)
23
+ };
24
+
25
+ spyOn(httpMock, 'get').and.callThrough();
26
+
27
+ TestBed.configureTestingModule({
28
+ providers: [
29
+ { provide: HttpClient, useValue: httpMock },
30
+ BookStoreService
31
+ ]
32
+ });
33
+ service = TestBed.inject(BookStoreService);
34
+ });
35
+
36
+ it('should GET a list of all books', () => {
37
+
38
+ let receivedBooks!: Book[];
39
+ service.getAll().subscribe(b => receivedBooks = b);
40
+
41
+ expect(receivedBooks).toHaveSize(2);
42
+ expect(receivedBooks[0].isbn).toBe('111');
43
+ expect(receivedBooks[1].isbn).toBe('222');
44
+
45
+ // NEU
46
+ expect(httpMock.get).toHaveBeenCalledOnceWith('https://api5.angular-buch.com/books');
47
+ });
48
+ });
src/app/shared/book-store.service.spec.ts DELETED
@@ -1,16 +0,0 @@
1
- import { TestBed } from '@angular/core/testing';
2
-
3
- import { BookStoreService } from './book-store.service';
4
-
5
- describe('BookStoreService', () => {
6
- let service: BookStoreService;
7
-
8
- beforeEach(() => {
9
- TestBed.configureTestingModule({});
10
- service = TestBed.inject(BookStoreService);
11
- });
12
-
13
- it('should be created', () => {
14
- expect(service).toBeTruthy();
15
- });
16
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/shared/book-store.service.stub.spec.ts ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { HttpClient } from '@angular/common/http';
2
+ import { TestBed } from '@angular/core/testing';
3
+ import { of } from 'rxjs';
4
+
5
+ import { Book } from './book';
6
+ import { BookStoreService } from './book-store.service';
7
+
8
+ describe('BookStoreService', () => {
9
+ let service: BookStoreService;
10
+
11
+ beforeEach(() => {
12
+
13
+ const expectedBooks: Book[] = [
14
+ { isbn: '111', title: 'Book 1', authors: [] },
15
+ { isbn: '222', title: 'Book 2', authors: [] }
16
+ ];
17
+
18
+ const httpStub = {
19
+ get: () => of(expectedBooks)
20
+ };
21
+
22
+ TestBed.configureTestingModule({
23
+ providers: [
24
+ { provide: HttpClient, useValue: httpStub }
25
+ ]
26
+ });
27
+ service = TestBed.inject(BookStoreService);
28
+ });
29
+
30
+ it('should GET a list of all books', () => {
31
+
32
+ let receivedBooks!: Book[];
33
+ service.getAll().subscribe(b => receivedBooks = b);
34
+
35
+ expect(receivedBooks).toHaveSize(2);
36
+ expect(receivedBooks[0].isbn).toBe('111');
37
+ expect(receivedBooks[1].isbn).toBe('222');
38
+ });
39
+ });
src/app/shared/book-store.service.ts CHANGED
@@ -1,4 +1,6 @@
 
1
  import { Injectable } from '@angular/core';
 
2
 
3
  import { Book } from './book';
4
 
@@ -6,36 +8,19 @@ import { Book } from './book';
6
  providedIn: 'root'
7
  })
8
  export class BookStoreService {
9
- private books: Book[] = [];
10
 
11
- constructor() {
12
- this.books = [
13
- {
14
- isbn: '12345',
15
- title: 'Tierisch gut kochen',
16
- authors: ['Mrs Chimp', 'Mr Gorilla'],
17
- published: '2022-06-20',
18
- subtitle: 'Rezepte von Affe bis Zebra',
19
- thumbnailUrl: 'https://cdn.ng-buch.de/kochen.png',
20
- description: 'Immer lecker und gut'
21
- },
22
- {
23
- isbn: '67890',
24
- title: 'Backen mit Affen',
25
- authors: ['Orang Utan'],
26
- published: '2022-07-15',
27
- subtitle: 'Bananenbrot und mehr',
28
- thumbnailUrl: 'https://cdn.ng-buch.de/backen.png',
29
- description: 'Tolle Backtipps für Mensch und Tier'
30
- }
31
- ];
32
  }
33
 
34
- getAll(): Book[] {
35
- return this.books;
36
  }
37
 
38
- getSingle(isbn: string): Book | undefined {
39
- return this.books.find(book => book.isbn === isbn);
40
  }
41
  }
1
+ import { HttpClient } from '@angular/common/http';
2
  import { Injectable } from '@angular/core';
3
+ import { Observable } from 'rxjs';
4
 
5
  import { Book } from './book';
6
 
8
  providedIn: 'root'
9
  })
10
  export class BookStoreService {
11
+ private apiUrl = 'https://api5.angular-buch.com';
12
 
13
+ constructor(private http: HttpClient) {}
14
+
15
+ getAll(): Observable<Book[]> {
16
+ return this.http.get<Book[]>(`${this.apiUrl}/books`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  }
18
 
19
+ getSingle(isbn: string): Observable<Book> {
20
+ return this.http.get<Book>(`${this.apiUrl}/books/${isbn}`);
21
  }
22
 
23
+ remove(isbn: string): Observable<unknown> {
24
+ return this.http.delete(`${this.apiUrl}/books/${isbn}`);
25
  }
26
  }