Differenzansicht 12-validation
im Vergleich zu 11-forms

Zurück zur Übersicht | ← Vorherige | Nächste → | Demo | Quelltext auf GitHub
src/app/books-admin/book-create-page/book-create-page.html CHANGED
@@ -1,37 +1,83 @@
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>
 
1
  <h1>Create book</h1>
2
 
3
  <form (submit)="submitForm($event)">
4
+ @let titleInvalid = isInvalid(bookForm.title);
5
  <label for="title">Title</label>
6
+ <input
7
+ type="text"
8
+ id="title"
9
+ [field]="bookForm.title"
10
+ [attr.aria-describedby]="titleInvalid ? 'title-error' : null"
11
+ [aria-invalid]="titleInvalid"
12
+ />
13
+ @if (titleInvalid) {
14
+ <small id="title-error">The title is invalid.</small>
15
+ }
16
 
17
  <label for="subtitle">Subtitle</label>
18
  <input type="text" id="subtitle" [field]="bookForm.subtitle" />
19
 
20
+ @let isbnInvalid = isInvalid(bookForm.isbn);
21
  <label for="isbn">ISBN</label>
22
+ <input
23
+ type="text"
24
+ id="isbn"
25
+ [field]="bookForm.isbn"
26
+ [attr.aria-describedby]="isbnInvalid ? 'isbn-error' : null"
27
+ [aria-invalid]="isbnInvalid"
28
+ />
29
+ @if (isbnInvalid) {
30
+ <small id="isbn-error">The ISBN is invalid.</small>
31
+ }
32
 
33
  <fieldset>
34
  <legend>Authors</legend>
35
  <button type="button" (click)="addAuthorField()">Add Author</button>
36
+ @let authorsInvalid = isInvalid(bookForm.authors);
37
  <div role="group">
38
  @for (authorField of bookForm.authors; track $index) {
39
  <input
40
  type="text"
41
  [aria-label]="`Author ${$index + 1}`"
42
  [field]="authorField"
43
+ [attr.aria-describedby]="authorsInvalid ? 'authors-error' : null"
44
+ [aria-invalid]="authorsInvalid"
45
  />
46
  }
47
+ @if (authorsInvalid) {
48
+ <small id="authors-error">At least one author must be added.</small>
49
+ }
50
  </div>
51
+
52
  </fieldset>
53
 
54
+ @let descriptionInvalid = isInvalid(bookForm.description);
55
  <label for="description">Description</label>
56
+ <textarea
57
+ id="description"
58
+ [field]="bookForm.description"
59
+ [attr.aria-describedby]="descriptionInvalid ? 'description-error' : null"
60
+ [aria-invalid]="descriptionInvalid">
61
+ </textarea>
62
+ @if (descriptionInvalid) {
63
+ <small id="description-error">The description is invalid.</small>
64
+ }
65
 
66
+ @let imageUrlInvalid = isInvalid(bookForm.imageUrl);
67
  <label for="imageUrl">Thumbnail URL</label>
68
+ <input
69
+ type="url"
70
+ id="imageUrl"
71
+ [field]="bookForm.imageUrl"
72
+ [attr.aria-describedby]="imageUrlInvalid ? 'image-url-error' : null"
73
+ [aria-invalid]="imageUrlInvalid" />
74
+ @if (imageUrlInvalid) {
75
+ <small id="image-url-error">The URL is invalid.</small>
76
+ }
77
 
78
  <button
79
  type="submit"
80
+ [disabled]="bookForm().invalid()"
81
  [aria-busy]="bookForm().submitting()">
82
  Save
83
  </button>
src/app/books-admin/book-create-page/book-create-page.ts CHANGED
@@ -1,10 +1,24 @@
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
  @Component({
9
  selector: 'app-book-create-page',
10
  imports: [Field],
@@ -24,12 +38,19 @@ export class BookCreatePage {
24
  imageUrl: '',
25
  createdAt: new Date().toISOString(),
26
  });
27
- protected readonly bookForm = form(this.#book);
28
 
29
  addAuthorField() {
30
  this.bookForm.authors().value.update((authors) => [...authors, '']);
31
  }
32
 
 
 
 
 
 
 
 
33
  async submitForm(e: SubmitEvent) {
34
  e.preventDefault();
35
 
 
1
  import { Component, inject, signal } from '@angular/core';
2
+ import { customError, Field, FieldTree, form, maxLength, minLength, required, schema, submit, validate } 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
+ export const formSchema = schema<Book>((fieldPath) => {
9
+ required(fieldPath.title);
10
+ required(fieldPath.isbn);
11
+ minLength(fieldPath.isbn, 13);
12
+ maxLength(fieldPath.isbn, 13);
13
+ validate(fieldPath.authors, (ctx) =>
14
+ !ctx.value().some((a) => a)
15
+ ? customError({ kind: 'atLeastOneAuthor' })
16
+ : undefined
17
+ );
18
+ required(fieldPath.description);
19
+ required(fieldPath.imageUrl);
20
+ });
21
+
22
  @Component({
23
  selector: 'app-book-create-page',
24
  imports: [Field],
 
38
  imageUrl: '',
39
  createdAt: new Date().toISOString(),
40
  });
41
+ protected readonly bookForm = form(this.#book, formSchema);
42
 
43
  addAuthorField() {
44
  this.bookForm.authors().value.update((authors) => [...authors, '']);
45
  }
46
 
47
+ isInvalid(field: FieldTree<unknown>) {
48
+ if (!field().touched()) {
49
+ return null;
50
+ }
51
+ return field().invalid();
52
+ }
53
+
54
  async submitForm(e: SubmitEvent) {
55
  e.preventDefault();
56