Differenzansicht 09-resource
im Vergleich zu 08-http

← Zurück zur Übersicht | Demo | Quelltext auf GitHub
src/app/books-portal/book-details-page/book-details-page.html CHANGED
@@ -1,4 +1,13 @@
1
- @if (book(); as b) {
 
 
 
 
 
 
 
 
 
2
  <article>
3
  <header>
4
  <h1>{{ b.title }}</h1>
@@ -28,6 +37,7 @@
28
  <img [src]="b.imageUrl" alt="Cover" />
29
  <footer>
30
  <a routerLink="/books">Back to list</a>
 
31
  <button type="button" (click)="removeBook(b.isbn)">
32
  Delete book
33
  </button>
 
1
+ @if (book.error()) {
2
+ <p role="alert">Book could not be found.</p>
3
+ <a routerLink="/books">Back to list</a>
4
+ }
5
+
6
+ @if (book.isLoading()) {
7
+ <p aria-busy="true">Loading...</p>
8
+ }
9
+
10
+ @if (book.value(); as b) {
11
  <article>
12
  <header>
13
  <h1>{{ b.title }}</h1>
 
37
  <img [src]="b.imageUrl" alt="Cover" />
38
  <footer>
39
  <a routerLink="/books">Back to list</a>
40
+ <a routerLink="/books/details/9783864909467">Angular Book</a>
41
  <button type="button" (click)="removeBook(b.isbn)">
42
  Delete book
43
  </button>
src/app/books-portal/book-details-page/book-details-page.ts CHANGED
@@ -1,7 +1,6 @@
1
- import { Component, effect, inject, input, signal } from '@angular/core';
2
  import { Router, RouterLink } from '@angular/router';
3
 
4
- import { Book } from '../../shared/book';
5
  import { BookStore } from '../../shared/book-store';
6
 
7
  @Component({
@@ -15,15 +14,7 @@ export class BookDetailsPage {
15
  #router = inject(Router);
16
 
17
  readonly isbn = input.required<string>();
18
- protected book = signal<Book | undefined>(undefined);
19
-
20
- constructor() {
21
- effect(() => {
22
- this.#bookStore.getSingle(this.isbn()).subscribe(book => {
23
- this.book.set(book);
24
- });
25
- });
26
- }
27
 
28
  removeBook(isbn: string) {
29
  if (window.confirm('Delete book?')) {
 
1
+ import { Component, inject, input } from '@angular/core';
2
  import { Router, RouterLink } from '@angular/router';
3
 
 
4
  import { BookStore } from '../../shared/book-store';
5
 
6
  @Component({
 
14
  #router = inject(Router);
15
 
16
  readonly isbn = input.required<string>();
17
+ protected book = this.#bookStore.getSingle(this.isbn);
 
 
 
 
 
 
 
 
18
 
19
  removeBook(isbn: string) {
20
  if (window.confirm('Delete book?')) {
src/app/books-portal/books-overview-page/books-overview-page.html CHANGED
@@ -12,6 +12,10 @@
12
 
13
  <section>
14
  <h1>Books</h1>
 
 
 
 
15
  <div>
16
  <input
17
  type="search"
 
12
 
13
  <section>
14
  <h1>Books</h1>
15
+ <button type="button" (click)="books.reload()" [attr.aria-busy]="books.isLoading()">
16
+ Reload
17
+ </button>
18
+
19
  <div>
20
  <input
21
  type="search"
src/app/books-portal/books-overview-page/books-overview-page.ts CHANGED
@@ -15,24 +15,18 @@ export class BooksOverviewPage {
15
 
16
  protected searchTerm = signal('');
17
 
18
- protected books = signal<Book[]>([]);
19
  protected likedBooks = signal<Book[]>([]);
20
 
21
  protected filteredBooks = computed(() => {
22
  if (!this.searchTerm()) {
23
- return this.books();
24
  }
25
 
26
  const term = this.searchTerm().toLowerCase();
27
- return this.books().filter((b) => b.title.toLowerCase().includes(term));
28
  });
29
 
30
- constructor() {
31
- this.#bookStore.getAll().subscribe(books => {
32
- this.books.set(books);
33
- });
34
- }
35
-
36
  addLikedBook(newLikedBook: Book) {
37
  const foundBook = this.likedBooks().find(
38
  (b) => b.isbn === newLikedBook.isbn
 
15
 
16
  protected searchTerm = signal('');
17
 
18
+ protected books = this.#bookStore.getAll();
19
  protected likedBooks = signal<Book[]>([]);
20
 
21
  protected filteredBooks = computed(() => {
22
  if (!this.searchTerm()) {
23
+ return this.books.value();
24
  }
25
 
26
  const term = this.searchTerm().toLowerCase();
27
+ return this.books.value().filter((b) => b.title.toLowerCase().includes(term));
28
  });
29
 
 
 
 
 
 
 
30
  addLikedBook(newLikedBook: Book) {
31
  const foundBook = this.likedBooks().find(
32
  (b) => b.isbn === newLikedBook.isbn
src/app/shared/book-store.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { inject, Injectable } from '@angular/core';
2
- import { HttpClient } from '@angular/common/http';
3
  import { Observable } from 'rxjs';
4
 
5
  import { Book } from './book';
@@ -11,12 +11,17 @@ export class BookStore {
11
  #http = inject(HttpClient);
12
  #apiUrl = 'https://api6.angular-buch.com';
13
 
14
- getAll(): Observable<Book[]> {
15
- return this.#http.get<Book[]>(`${this.#apiUrl}/books`);
 
 
 
16
  }
17
 
18
- getSingle(isbn: string): Observable<Book> {
19
- return this.#http.get<Book>(`${this.#apiUrl}/books/${isbn}`);
 
 
20
  }
21
 
22
  remove(isbn: string): Observable<void> {
 
1
+ import { inject, Injectable, Signal } from '@angular/core';
2
+ import { HttpClient, httpResource, HttpResourceRef } from '@angular/common/http';
3
  import { Observable } from 'rxjs';
4
 
5
  import { Book } from './book';
 
11
  #http = inject(HttpClient);
12
  #apiUrl = 'https://api6.angular-buch.com';
13
 
14
+ getAll(): HttpResourceRef<Book[]> {
15
+ return httpResource<Book[]>(
16
+ () => `${this.#apiUrl}/books`,
17
+ { defaultValue: [] }
18
+ );
19
  }
20
 
21
+ getSingle(isbn: Signal<string>): HttpResourceRef<Book | undefined> {
22
+ return httpResource<Book>(
23
+ () => `${this.#apiUrl}/books/${isbn()}`
24
+ );
25
  }
26
 
27
  remove(isbn: string): Observable<void> {