Differenzansicht 11-forms
im Vergleich zu 10-pipes

Zurück zur Übersicht | ← Vorherige | Nächste → | Demo | Quelltext auf GitHub
src/app/app.html CHANGED
@@ -9,6 +9,10 @@
9
  <a routerLink="/books" routerLinkActive="active"
10
  ariaCurrentWhenActive="page">Books</a>
11
  </li>
 
 
 
 
12
  </ul>
13
  </nav>
14
  <router-outlet />
 
9
  <a routerLink="/books" routerLinkActive="active"
10
  ariaCurrentWhenActive="page">Books</a>
11
  </li>
12
+ <li>
13
+ <a routerLink="/admin" routerLinkActive="active"
14
+ ariaCurrentWhenActive="page">Admin</a>
15
+ </li>
16
  </ul>
17
  </nav>
18
  <router-outlet />
src/app/app.routes.ts CHANGED
@@ -2,9 +2,11 @@ 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
  ];
 
2
 
3
  import { HomePage } from './home-page/home-page';
4
  import { booksPortalRoutes } from './books-portal/books-portal.routes';
5
+ import { booksAdminRoutes } from './books-admin/books-admin.routes';
6
 
7
  export const routes: Routes = [
8
  { path: '', redirectTo: 'home', pathMatch: 'full' },
9
  { path: 'home', component: HomePage, title: 'BookManager' },
10
+ ...booksPortalRoutes,
11
+ ...booksAdminRoutes
12
  ];
src/app/books-admin/book-create-page/book-create-page.html ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <h1>Create book</h1>
2
+
3
+ <form (submit)="submitForm($event)">
4
+ <label for="title">Title</label>
5
+ <input type="text" id="title" [field]="bookForm.title" />
6
+
7
+ <label for="subtitle">Subtitle</label>
8
+ <input type="text" id="subtitle" [field]="bookForm.subtitle" />
9
+
10
+ <label for="isbn">ISBN</label>
11
+ <input type="text" id="isbn" [field]="bookForm.isbn" />
12
+
13
+ <fieldset>
14
+ <legend>Authors</legend>
15
+ <button type="button" (click)="addAuthorField()">Add Author</button>
16
+ <div role="group">
17
+ @for (authorField of bookForm.authors; track $index) {
18
+ <input
19
+ type="text"
20
+ [aria-label]="`Author ${$index + 1}`"
21
+ [field]="authorField"
22
+ />
23
+ }
24
+ </div>
25
+ </fieldset>
26
+
27
+ <label for="description">Description</label>
28
+ <textarea id="description" [field]="bookForm.description"></textarea>
29
+
30
+ <label for="imageUrl">Thumbnail URL</label>
31
+ <input type="url" id="imageUrl" [field]="bookForm.imageUrl" />
32
+
33
+ <button
34
+ type="submit"
35
+ [aria-busy]="bookForm().submitting()">
36
+ Save
37
+ </button>
38
+ </form>
src/app/books-admin/book-create-page/book-create-page.spec.ts ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+
3
+ import { BookCreatePage } from './book-create-page';
4
+
5
+ describe('BookCreatePage', () => {
6
+ let component: BookCreatePage;
7
+ let fixture: ComponentFixture<BookCreatePage>;
8
+
9
+ beforeEach(async () => {
10
+ await TestBed.configureTestingModule({
11
+ imports: [BookCreatePage]
12
+ })
13
+ .compileComponents();
14
+
15
+ fixture = TestBed.createComponent(BookCreatePage);
16
+ component = fixture.componentInstance;
17
+ fixture.detectChanges();
18
+ });
19
+
20
+ it('should create', () => {
21
+ expect(component).toBeTruthy();
22
+ });
23
+ });
src/app/books-admin/book-create-page/book-create-page.ts ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Component, inject, signal } from '@angular/core';
2
+ import { Field, form, submit } from '@angular/forms/signals';
3
+ import { Router } from '@angular/router';
4
+
5
+ import { Book } from '../../shared/book';
6
+ import { BookStore } from '../../shared/book-store';
7
+
8
+ type BookFormData = Required<Book>;
9
+
10
+ @Component({
11
+ selector: 'app-book-create-page',
12
+ imports: [Field],
13
+ templateUrl: './book-create-page.html',
14
+ styleUrl: './book-create-page.scss'
15
+ })
16
+ export class BookCreatePage {
17
+ #bookStore = inject(BookStore);
18
+ #router = inject(Router);
19
+
20
+ readonly #bookFormData = signal<BookFormData>({
21
+ isbn: '',
22
+ title: '',
23
+ subtitle: '',
24
+ authors: [''],
25
+ description: '',
26
+ imageUrl: '',
27
+ createdAt: new Date().toISOString(),
28
+ });
29
+ protected readonly bookForm = form(this.#bookFormData);
30
+
31
+ addAuthorField() {
32
+ this.bookForm.authors().value.update((authors) => [...authors, '']);
33
+ }
34
+
35
+ async submitForm(e: SubmitEvent) {
36
+ e.preventDefault();
37
+
38
+ await submit(this.bookForm, async (form) => {
39
+ const formValue = form().value();
40
+ const authors = formValue.authors.filter(author => !!author);
41
+
42
+ const newBook: Book = {
43
+ ...formValue,
44
+ authors,
45
+ createdAt: new Date().toISOString()
46
+ };
47
+ this.#bookStore.create(newBook).subscribe(createdBook => {
48
+ this.#router.navigate(['/books', 'details', createdBook.isbn]);
49
+ });
50
+ });
51
+ }
52
+ }
src/app/books-admin/books-admin.routes.ts ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ import { Routes } from '@angular/router';
2
+
3
+ import { BookCreatePage } from './book-create-page/book-create-page';
4
+
5
+ export const booksAdminRoutes: Routes = [
6
+ { path: 'admin', redirectTo: 'admin/create' },
7
+ { path: 'admin/create', component: BookCreatePage, title: 'Create Book' }
8
+ ];
src/app/shared/book-store.ts CHANGED
@@ -27,4 +27,8 @@ export class BookStore {
27
  remove(isbn: string): Observable<void> {
28
  return this.#http.delete<void>(`${this.#apiUrl}/books/${isbn}`);
29
  }
 
 
 
 
30
  }
 
27
  remove(isbn: string): Observable<void> {
28
  return this.#http.delete<void>(`${this.#apiUrl}/books/${isbn}`);
29
  }
30
+
31
+ create(book: Book): Observable<Book> {
32
+ return this.#http.post<Book>(`${this.#apiUrl}/books`, book);
33
+ }
34
  }