Differenzansicht 03-outputs
im Vergleich zu 02-inputs

Zurück zur Übersicht | ← Vorherige | Nächste → | Demo | Quelltext auf GitHub
src/app/books-portal/book-card/book-card.html CHANGED
@@ -11,4 +11,7 @@
11
  }
12
  ISBN: {{ b.isbn }}
13
  </div>
 
 
 
14
  </article>
 
11
  }
12
  ISBN: {{ b.isbn }}
13
  </div>
14
+ <footer>
15
+ <button type="button" class="secondary" (click)="likeBook()">Like</button>
16
+ </footer>
17
  </article>
src/app/books-portal/book-card/book-card.spec.ts CHANGED
@@ -2,11 +2,13 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
 
3
  import { BookCard } from './book-card';
4
  import { Book } from '../../shared/book';
5
- import { signal, inputBinding } from '@angular/core';
6
 
7
  describe('BookCard', () => {
8
  let fixture: ComponentFixture<BookCard>;
9
- const testBook = signal<Book>({
 
 
10
  isbn: '1111',
11
  title: 'Testbuch',
12
  subtitle: 'Test',
@@ -14,33 +16,31 @@ describe('BookCard', () => {
14
  imageUrl: 'https://cdn.ng-buch.de/test.png',
15
  description: 'Dies ist ein Testbuch',
16
  createdAt: new Date().toISOString()
17
- });
18
 
19
  beforeEach(async () => {
 
 
20
  await TestBed.configureTestingModule({
21
  imports: [BookCard]
22
- })
23
- .compileComponents();
24
-
25
 
26
  fixture = TestBed.createComponent(BookCard, {
27
- bindings: [inputBinding('book', testBook)]
 
 
 
28
  });
29
 
30
  fixture.detectChanges();
31
  });
32
 
33
- it('should render book title, subtitle and isbn', () => {
34
- const compiledElement: HTMLElement = fixture.nativeElement;
35
- expect(compiledElement.textContent).toContain(testBook().isbn);
36
- expect(compiledElement.textContent).toContain(testBook().subtitle);
37
- expect(compiledElement.textContent).toContain(testBook().isbn);
38
- });
39
 
40
- it('should display the correct image', () => {
41
- const compiledElement: HTMLElement = fixture.nativeElement;
42
- const imageEl = compiledElement.querySelector('img')!;
43
- expect(imageEl).toBeTruthy();
44
- expect(imageEl.src).toBe(testBook().imageUrl);
45
  });
46
  });
 
2
 
3
  import { BookCard } from './book-card';
4
  import { Book } from '../../shared/book';
5
+ import { inputBinding, outputBinding } from '@angular/core';
6
 
7
  describe('BookCard', () => {
8
  let fixture: ComponentFixture<BookCard>;
9
+ let emittedBook: Book | undefined;
10
+
11
+ const testBook: Book = {
12
  isbn: '1111',
13
  title: 'Testbuch',
14
  subtitle: 'Test',
 
16
  imageUrl: 'https://cdn.ng-buch.de/test.png',
17
  description: 'Dies ist ein Testbuch',
18
  createdAt: new Date().toISOString()
19
+ };
20
 
21
  beforeEach(async () => {
22
+ emittedBook = undefined;
23
+
24
  await TestBed.configureTestingModule({
25
  imports: [BookCard]
26
+ }).compileComponents();
 
 
27
 
28
  fixture = TestBed.createComponent(BookCard, {
29
+ bindings: [
30
+ inputBinding('book', () => testBook),
31
+ outputBinding('like', (book: Book) => emittedBook = book)
32
+ ]
33
  });
34
 
35
  fixture.detectChanges();
36
  });
37
 
38
+ it('should emit the like event with the correct book', () => {
39
+ // Event manuell auslösen
40
+ fixture.componentInstance.likeBook();
 
 
 
41
 
42
+ // Prüfen, ob das Event ausgelöst wurde
43
+ expect(emittedBook).toBeDefined();
44
+ expect(emittedBook).toEqual(testBook);
 
 
45
  });
46
  });
src/app/books-portal/book-card/book-card.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Component, input } from '@angular/core';
2
 
3
  import { Book } from '../../shared/book';
4
 
@@ -10,4 +10,9 @@ import { Book } from '../../shared/book';
10
  })
11
  export class BookCard {
12
  readonly book = input.required<Book>();
 
 
 
 
 
13
  }
 
1
+ import { Component, input, output } from '@angular/core';
2
 
3
  import { Book } from '../../shared/book';
4
 
 
10
  })
11
  export class BookCard {
12
  readonly book = input.required<Book>();
13
+ readonly like = output<Book>();
14
+
15
+ likeBook() {
16
+ this.like.emit(this.book());
17
+ }
18
  }
src/app/books-portal/books-overview-page/books-overview-page.html CHANGED
@@ -1,8 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
  <section>
2
  <h1>Books</h1>
3
  <div>
4
  @for (b of books(); track b.isbn) {
5
- <app-book-card [book]="b" />
6
  }
7
  </div>
8
  </section>
 
1
+ <section>
2
+ <h1>Favorite Books</h1>
3
+ <button type="button" (click)="clearLikedBooks()">Clear</button>
4
+ <ul>
5
+ @for (b of likedBooks(); track b.isbn) {
6
+ <li>{{ b.title }} ({{ b.isbn }})</li>
7
+ } @empty {
8
+ <li>No books liked.</li>
9
+ }
10
+ </ul>
11
+ </section>
12
+
13
  <section>
14
  <h1>Books</h1>
15
  <div>
16
  @for (b of books(); track b.isbn) {
17
+ <app-book-card [book]="b" (like)="addLikedBook($event)" />
18
  }
19
  </div>
20
  </section>
src/app/books-portal/books-overview-page/books-overview-page.ts CHANGED
@@ -11,6 +11,7 @@ import { BookCard } from '../book-card/book-card';
11
  })
12
  export class BooksOverviewPage {
13
  protected books = signal<Book[]>([]);
 
14
 
15
  constructor() {
16
  this.books.set([
@@ -34,4 +35,18 @@ export class BooksOverviewPage {
34
  },
35
  ]);
36
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  }
 
11
  })
12
  export class BooksOverviewPage {
13
  protected books = signal<Book[]>([]);
14
+ protected likedBooks = signal<Book[]>([]);
15
 
16
  constructor() {
17
  this.books.set([
 
35
  },
36
  ]);
37
  }
38
+
39
+ addLikedBook(newLikedBook: Book) {
40
+ const foundBook = this.likedBooks().find(
41
+ (b) => b.isbn === newLikedBook.isbn
42
+ );
43
+
44
+ if (!foundBook) {
45
+ this.likedBooks.update((likedBooks) => [...likedBooks, newLikedBook]);
46
+ }
47
+ }
48
+
49
+ clearLikedBooks() {
50
+ this.likedBooks.set([]);
51
+ }
52
  }