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
@@ -1,5 +1,6 @@
1
- import { inputBinding, signal } from '@angular/core';
2
  import { ComponentFixture, TestBed } from '@angular/core/testing';
 
3
 
4
  import { Book } from '../../shared/book';
5
  import { BookCard } from './book-card';
@@ -15,15 +16,21 @@ describe('BookCard', () => {
15
  imageUrl: 'https://example.com/test.png',
16
  createdAt: '2026-01-01'
17
  });
 
18
 
19
  beforeEach(async () => {
 
 
20
  await TestBed.configureTestingModule({
21
  imports: [BookCard]
22
  })
23
  .compileComponents();
24
 
25
  fixture = TestBed.createComponent(BookCard, {
26
- bindings: [inputBinding('book', testBook)]
 
 
 
27
  });
28
  component = fixture.componentInstance;
29
  await fixture.whenStable();
@@ -45,4 +52,9 @@ describe('BookCard', () => {
45
  expect(imageEl).toBeTruthy();
46
  expect(imageEl?.src).toBe(testBook().imageUrl);
47
  });
 
 
 
 
 
48
  });
 
1
+ import { inputBinding, outputBinding, signal } from '@angular/core';
2
  import { ComponentFixture, TestBed } from '@angular/core/testing';
3
+ import { Mock } from 'vitest';
4
 
5
  import { Book } from '../../shared/book';
6
  import { BookCard } from './book-card';
 
16
  imageUrl: 'https://example.com/test.png',
17
  createdAt: '2026-01-01'
18
  });
19
+ let likeMock: Mock;
20
 
21
  beforeEach(async () => {
22
+ likeMock = vi.fn();
23
+
24
  await TestBed.configureTestingModule({
25
  imports: [BookCard]
26
  })
27
  .compileComponents();
28
 
29
  fixture = TestBed.createComponent(BookCard, {
30
+ bindings: [
31
+ inputBinding('book', testBook),
32
+ outputBinding('like', likeMock)
33
+ ]
34
  });
35
  component = fixture.componentInstance;
36
  await fixture.whenStable();
 
52
  expect(imageEl).toBeTruthy();
53
  expect(imageEl?.src).toBe(testBook().imageUrl);
54
  });
55
+
56
+ it('should emit the like event with the correct book', () => {
57
+ component.likeBook();
58
+ expect(likeMock).toHaveBeenCalledExactlyOnceWith(testBook());
59
+ });
60
  });
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
  }