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,5 +1,5 @@
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';
@@ -7,6 +7,20 @@ import { BookStore } from '../../shared/book-store';
7
 
8
  type BookFormData = Required<Book>;
9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  @Component({
11
  selector: 'app-book-create-page',
12
  imports: [Field],
@@ -26,12 +40,19 @@ export class BookCreatePage {
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
 
 
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';
 
7
 
8
  type BookFormData = Required<Book>;
9
 
10
+ export const bookFormSchema = schema<BookFormData>((schemaPath) => {
11
+ required(schemaPath.title);
12
+ required(schemaPath.isbn);
13
+ minLength(schemaPath.isbn, 13);
14
+ maxLength(schemaPath.isbn, 13);
15
+ validate(schemaPath.authors, (ctx) =>
16
+ !ctx.value().some((a) => a)
17
+ ? { kind: 'atLeastOneAuthor' }
18
+ : undefined
19
+ );
20
+ required(schemaPath.description);
21
+ required(schemaPath.imageUrl);
22
+ });
23
+
24
  @Component({
25
  selector: 'app-book-create-page',
26
  imports: [Field],
 
40
  imageUrl: '',
41
  createdAt: new Date().toISOString(),
42
  });
43
+ protected readonly bookForm = form(this.#bookFormData, bookFormSchema);
44
 
45
  addAuthorField() {
46
  this.bookForm.authors().value.update((authors) => [...authors, '']);
47
  }
48
 
49
+ isInvalid(field: FieldTree<unknown>) {
50
+ if (!field().touched()) {
51
+ return null;
52
+ }
53
+ return field().invalid();
54
+ }
55
+
56
  async submitForm(e: SubmitEvent) {
57
  e.preventDefault();
58