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 ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
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',
15
+ authors: ['Author 1', 'Author 2'],
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/book-card/book-card.variante1.spec.ts DELETED
@@ -1,46 +0,0 @@
1
- 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',
13
- authors: ['Author 1', 'Author 2'],
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
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/app/books-portal/book-card/book-card.variante2.spec.ts DELETED
@@ -1,46 +0,0 @@
1
- import { ComponentFixture, TestBed } from '@angular/core/testing';
2
-
3
- import { BookCard } from './book-card';
4
- import { Book } from '../../shared/book';
5
- import { inputBinding } from '@angular/core';
6
-
7
- describe('BookCard', () => {
8
- let fixture: ComponentFixture<BookCard>;
9
- const testBook: Book = {
10
- isbn: '1111',
11
- title: 'Testbuch',
12
- subtitle: 'Test',
13
- authors: ['Author 1', 'Author 2'],
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
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
  }