Differenzansicht 06-routing
im Vergleich zu 05-filter

Zurück zur Übersicht | ← Vorherige | Nächste → | Demo | Quelltext auf GitHub
src/app/app.html CHANGED
@@ -1,3 +1,15 @@
1
  <main>
2
- <app-books-overview-page />
 
 
 
 
 
 
 
 
 
 
 
 
3
  </main>
 
1
  <main>
2
+ <nav>
3
+ <ul>
4
+ <li>
5
+ <a routerLink="/home" routerLinkActive="active"
6
+ ariaCurrentWhenActive="page">Home</a>
7
+ </li>
8
+ <li>
9
+ <a routerLink="/books" routerLinkActive="active"
10
+ ariaCurrentWhenActive="page">Books</a>
11
+ </li>
12
+ </ul>
13
+ </nav>
14
+ <router-outlet />
15
  </main>
src/app/app.routes.ts CHANGED
@@ -1,3 +1,10 @@
1
  import { Routes } from '@angular/router';
2
 
3
- export const routes: Routes = [];
 
 
 
 
 
 
 
 
1
  import { Routes } from '@angular/router';
2
 
3
+ import { HomePage } from './home-page/home-page';
4
+ import { booksPortalRoutes } from './books-portal/books-portal.routes';
5
+
6
+ export const routes: Routes = [
7
+ { path: '', redirectTo: 'home', pathMatch: 'full' },
8
+ { path: 'home', component: HomePage, title: 'BookManager' },
9
+ ...booksPortalRoutes
10
+ ];
src/app/app.spec.ts CHANGED
@@ -1,10 +1,14 @@
1
  import { TestBed } from '@angular/core/testing';
2
  import { App } from './app';
 
3
 
4
  describe('App', () => {
5
  beforeEach(async () => {
6
  await TestBed.configureTestingModule({
7
  imports: [App],
 
 
 
8
  }).compileComponents();
9
  });
10
 
 
1
  import { TestBed } from '@angular/core/testing';
2
  import { App } from './app';
3
+ import { provideRouter } from '@angular/router';
4
 
5
  describe('App', () => {
6
  beforeEach(async () => {
7
  await TestBed.configureTestingModule({
8
  imports: [App],
9
+ providers: [
10
+ provideRouter([]) // Verhindert: NG0201: No provider found for `ActivatedRoute`.
11
+ ]
12
  }).compileComponents();
13
  });
14
 
src/app/app.ts CHANGED
@@ -1,10 +1,9 @@
1
  import { Component } from '@angular/core';
2
-
3
- import { BooksOverviewPage } from './books-portal/books-overview-page/books-overview-page';
4
 
5
  @Component({
6
  selector: 'app-root',
7
- imports: [BooksOverviewPage],
8
  templateUrl: './app.html',
9
  styleUrl: './app.scss'
10
  })
 
1
  import { Component } from '@angular/core';
2
+ import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
 
3
 
4
  @Component({
5
  selector: 'app-root',
6
+ imports: [RouterOutlet, RouterLink, RouterLinkActive],
7
  templateUrl: './app.html',
8
  styleUrl: './app.scss'
9
  })
src/app/books-portal/book-card/book-card.html CHANGED
@@ -12,6 +12,7 @@
12
  ISBN: {{ b.isbn }}
13
  </div>
14
  <footer>
 
15
  <button type="button" class="secondary" (click)="likeBook()">Like</button>
16
  </footer>
17
  </article>
 
12
  ISBN: {{ b.isbn }}
13
  </div>
14
  <footer>
15
+ <a [routerLink]="['details', b.isbn]">Details</a>
16
  <button type="button" class="secondary" (click)="likeBook()">Like</button>
17
  </footer>
18
  </article>
src/app/books-portal/book-card/book-card.spec.ts CHANGED
@@ -1,8 +1,9 @@
 
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>;
@@ -22,7 +23,10 @@ describe('BookCard', () => {
22
  emittedBook = undefined;
23
 
24
  await TestBed.configureTestingModule({
25
- imports: [BookCard]
 
 
 
26
  }).compileComponents();
27
 
28
  fixture = TestBed.createComponent(BookCard, {
 
1
+ import { inputBinding, outputBinding } from '@angular/core';
2
  import { ComponentFixture, TestBed } from '@angular/core/testing';
3
+ import { provideRouter } from '@angular/router';
4
 
 
5
  import { Book } from '../../shared/book';
6
+ import { BookCard } from './book-card';
7
 
8
  describe('BookCard', () => {
9
  let fixture: ComponentFixture<BookCard>;
 
23
  emittedBook = undefined;
24
 
25
  await TestBed.configureTestingModule({
26
+ imports: [BookCard],
27
+ providers: [
28
+ provideRouter([]) // Verhindert: NG0201: No provider found for `ActivatedRoute`.
29
+ ]
30
  }).compileComponents();
31
 
32
  fixture = TestBed.createComponent(BookCard, {
src/app/books-portal/book-card/book-card.ts CHANGED
@@ -1,10 +1,11 @@
1
  import { Component, input, output } from '@angular/core';
 
2
 
3
  import { Book } from '../../shared/book';
4
 
5
  @Component({
6
  selector: 'app-book-card',
7
- imports: [],
8
  templateUrl: './book-card.html',
9
  styleUrl: './book-card.scss'
10
  })
 
1
  import { Component, input, output } from '@angular/core';
2
+ import { RouterLink } from '@angular/router';
3
 
4
  import { Book } from '../../shared/book';
5
 
6
  @Component({
7
  selector: 'app-book-card',
8
+ imports: [RouterLink],
9
  templateUrl: './book-card.html',
10
  styleUrl: './book-card.scss'
11
  })
src/app/books-portal/book-details-page/book-details-page-provide-location-mocks.spec.ts ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Location } from '@angular/common';
2
+ import { provideLocationMocks } from '@angular/common/testing';
3
+ import { TestBed } from '@angular/core/testing';
4
+ import { provideRouter, Router } from '@angular/router';
5
+
6
+ import { booksPortalRoutes } from '../books-portal.routes';
7
+
8
+ describe('BookDetailsPage Routing', () => {
9
+ it('should naviate to the details page', async () => {
10
+ TestBed.configureTestingModule({
11
+ providers: [
12
+ provideRouter(booksPortalRoutes),
13
+ provideLocationMocks()
14
+ ]
15
+ });
16
+
17
+ const location = TestBed.inject(Location);
18
+ const router = TestBed.inject(Router);
19
+
20
+ // Hier wird später im produktiven Code eine Aktion stattfinden,
21
+ // z.B. das Absenden eines Formulars und eine anschließende Navigation
22
+ await router.navigate(['/books/details/12345']);
23
+
24
+ // Prüfung, ob Navigation zur erwarteten Ziel-URL stattgefunden hat
25
+ expect(location.path()).toBe('/books/details/12345');
26
+ });
27
+ });
src/app/books-portal/book-details-page/book-details-page.html ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @if (book(); as b) {
2
+ <article>
3
+ <header>
4
+ <h1>{{ b.title }}</h1>
5
+ @if (b.subtitle) {
6
+ <p role="doc-subtitle">{{ b.subtitle }}</p>
7
+ }
8
+ <div class="grid">
9
+ <div>
10
+ <h2>Authors</h2>
11
+ <ul>
12
+ @for (author of b.authors; track $index) {
13
+ <li>{{ author }}</li>
14
+ }
15
+ </ul>
16
+ </div>
17
+ <div>
18
+ <h2>ISBN</h2>
19
+ {{ b.isbn }}
20
+ </div>
21
+ <div>
22
+ <h2>Created at</h2>
23
+ {{ b.createdAt }}
24
+ </div>
25
+ </div>
26
+ </header>
27
+ <p>{{ b.description }}</p>
28
+ <img [src]="b.imageUrl" alt="Cover" />
29
+ <footer>
30
+ <a routerLink="/books">Back to list</a>
31
+ </footer>
32
+ </article>
33
+ }
src/app/books-portal/book-details-page/book-details-page.spec.ts ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { TestBed } from '@angular/core/testing';
2
+ import { RouterTestingHarness } from '@angular/router/testing';
3
+ import { provideRouter } from '@angular/router';
4
+
5
+ import { BookDetailsPage } from './book-details-page';
6
+ import { booksPortalRoutes } from '../books-portal.routes';
7
+ import { BookStore } from '../../shared/book-store';
8
+
9
+ describe('BookDetailsPage Routing', () => {
10
+ it('should load the correct book by ISBN', async () => {
11
+ TestBed.configureTestingModule({
12
+ imports: [BookDetailsPage],
13
+ providers: [provideRouter(booksPortalRoutes)]
14
+ });
15
+
16
+ const harness = await RouterTestingHarness.create();
17
+ const component = await harness.navigateByUrl('/books/details/12345', BookDetailsPage);
18
+ const bookStore = TestBed.inject(BookStore);
19
+
20
+ const expectedBook = bookStore.getSingle('12345');
21
+
22
+ expect(component).toBeTruthy();
23
+ expect(component['book']()).toEqual(expectedBook);
24
+ expect(document.title).toBe('Book Details');
25
+ });
26
+ });
src/app/books-portal/book-details-page/book-details-page.ts ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Component, inject, signal } from '@angular/core';
2
+ import { ActivatedRoute, RouterLink } from '@angular/router';
3
+
4
+ import { Book } from '../../shared/book';
5
+ import { BookStore } from '../../shared/book-store';
6
+
7
+ @Component({
8
+ selector: 'app-book-details-page',
9
+ imports: [RouterLink],
10
+ templateUrl: './book-details-page.html',
11
+ styleUrl: './book-details-page.scss'
12
+ })
13
+ export class BookDetailsPage {
14
+ #bookStore = inject(BookStore);
15
+ #route = inject(ActivatedRoute);
16
+
17
+ protected book = signal<Book | undefined>(undefined);
18
+
19
+ constructor() {
20
+ const isbn = this.#route.snapshot.paramMap.get('isbn');
21
+ if (isbn) {
22
+ this.book.set(this.#bookStore.getSingle(isbn));
23
+ }
24
+ }
25
+ }
src/app/books-portal/books-overview-page/books-overview-page.spec.ts CHANGED
@@ -1,47 +1,21 @@
1
- import { ComponentFixture, TestBed } from '@angular/core/testing';
2
- import { BooksOverviewPage } from './books-overview-page';
3
-
4
- describe('BooksOverviewPage', () => {
5
- let component: BooksOverviewPage;
6
- let fixture: ComponentFixture<BooksOverviewPage>;
7
 
8
- beforeEach(async () => {
9
- await TestBed.configureTestingModule({
10
- imports: [BooksOverviewPage]
11
- }).compileComponents();
12
-
13
- fixture = TestBed.createComponent(BooksOverviewPage);
14
- component = fixture.componentInstance;
15
- fixture.detectChanges();
16
- });
17
-
18
- it('should display all books if the search term is empty', () => {
19
- component['searchTerm'].set('');
20
-
21
- const books = component['filteredBooks']();
22
- expect(books.length).toBe(2);
23
- });
24
-
25
- it('should filter books based on the search term', () => {
26
- component['searchTerm'].set('Affe');
27
-
28
- const books = component['filteredBooks']();
29
- expect(books.length).toBe(1);
30
- expect(books[0].title).toBe('Backen mit Affen');
31
- });
32
-
33
- it('should filter books ignoring case sensitivity', () => {
34
- component['searchTerm'].set('AFFEN');
35
 
36
- const books = component['filteredBooks']();
37
- expect(books.length).toBe(1);
38
- expect(books[0].title).toBe('Backen mit Affen');
39
- });
 
 
40
 
41
- it('should return an empty array if no book matches the search term', () => {
42
- component['searchTerm'].set('unbekannter Titel');
43
 
44
- const books = component['filteredBooks']();
45
- expect(books.length).toBe(0);
46
  });
47
  });
 
1
+ import { TestBed } from '@angular/core/testing';
2
+ import { provideRouter } from '@angular/router';
3
+ import { RouterTestingHarness } from '@angular/router/testing';
 
 
 
4
 
5
+ import { booksPortalRoutes } from '../books-portal.routes';
6
+ import { BooksOverviewPage } from './books-overview-page';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
 
8
+ describe('BooksOverviewPage Routing', () => {
9
+ it('should load the BooksOverviewPage for /books', async () => {
10
+ TestBed.configureTestingModule({
11
+ imports: [BooksOverviewPage],
12
+ providers: [provideRouter(booksPortalRoutes)]
13
+ });
14
 
15
+ const harness = await RouterTestingHarness.create();
16
+ const component = await harness.navigateByUrl('/books', BooksOverviewPage);
17
 
18
+ expect(component).toBeTruthy();
19
+ expect(document.title).toBe('Books');
20
  });
21
  });
src/app/books-portal/books-portal.routes.ts ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import { Routes } from '@angular/router';
2
+
3
+ import { BooksOverviewPage } from './books-overview-page/books-overview-page';
4
+ import { BookDetailsPage } from './book-details-page/book-details-page';
5
+
6
+ export const booksPortalRoutes: Routes = [
7
+ { path: 'books', component: BooksOverviewPage, title: 'Books' },
8
+ { path: 'books/details/:isbn', component: BookDetailsPage, title: 'Book Details' },
9
+ ];
src/app/home-page/home-page.html ADDED
@@ -0,0 +1 @@
 
 
1
+ <h1>Welcome to the BookManager!</h1>
src/app/home-page/home-page.spec.ts ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { TestBed } from '@angular/core/testing';
2
+ import { provideRouter } from '@angular/router';
3
+ import { RouterTestingHarness } from '@angular/router/testing';
4
+
5
+ import { routes } from '../app.routes';
6
+ import { HomePage } from './home-page';
7
+
8
+ describe('HomePage Routing', () => {
9
+ it('should load the HomePage component for /home', async () => {
10
+ TestBed.configureTestingModule({
11
+ imports: [HomePage],
12
+ providers: [provideRouter(routes)]
13
+ });
14
+
15
+ const harness = await RouterTestingHarness.create();
16
+ const component = await harness.navigateByUrl('/home', HomePage);
17
+
18
+ expect(component).toBeTruthy();
19
+ expect(document.title).toBe('BookManager');
20
+ });
21
+ });
src/app/home-page/home-page.ts ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Component } from '@angular/core';
2
+
3
+ @Component({
4
+ selector: 'app-home-page',
5
+ imports: [],
6
+ templateUrl: './home-page.html',
7
+ styleUrl: './home-page.scss'
8
+ })
9
+ export class HomePage {
10
+
11
+ }
src/app/shared/book-store.ts CHANGED
@@ -31,4 +31,8 @@ export class BookStore {
31
  getAll(): Book[] {
32
  return this.#books;
33
  }
 
 
 
 
34
  }
 
31
  getAll(): Book[] {
32
  return this.#books;
33
  }
34
+
35
+ getSingle(isbn: string): Book | undefined {
36
+ return this.#books.find(book => book.isbn === isbn);
37
+ }
38
  }