Skip to content

Commit 38f4ea1

Browse files
bencodezenthePunderWoman
authored andcommitted
docs: add signal forms validation guide
1 parent 4771dc9 commit 38f4ea1

File tree

11 files changed

+778
-43
lines changed

11 files changed

+778
-43
lines changed

adev/src/app/routing/sub-navigation-data.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,11 @@ const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
447447
path: 'guide/forms/signals/field-state-management',
448448
contentPath: 'guide/forms/signals/field-state-management',
449449
},
450+
{
451+
label: 'Validation',
452+
path: 'guide/forms/signals/validation',
453+
contentPath: 'guide/forms/signals/validation',
454+
},
450455
{
451456
label: 'Comparison with other form systems',
452457
path: 'guide/forms/signals/comparison',

adev/src/content/examples/signal-forms/src/login-simple/index.html

Lines changed: 0 additions & 14 deletions
This file was deleted.

adev/src/content/examples/signal-forms/src/login-simple/main.ts

Lines changed: 0 additions & 5 deletions
This file was deleted.
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
form {
2+
display: flex;
3+
flex-direction: column;
4+
gap: 1rem;
5+
max-width: 400px;
6+
padding: 1rem;
7+
font-family:
8+
Inter,
9+
system-ui,
10+
-apple-system,
11+
sans-serif;
12+
}
13+
14+
div {
15+
display: flex;
16+
flex-direction: column;
17+
gap: 0.25rem;
18+
}
19+
20+
label {
21+
display: flex;
22+
flex-direction: column;
23+
gap: 0.25rem;
24+
font-weight: 500;
25+
}
26+
27+
input {
28+
padding: 0.5rem;
29+
border: 1px solid #ccc;
30+
border-radius: 4px;
31+
font-size: 1rem;
32+
font-family: inherit;
33+
}
34+
35+
input:focus {
36+
outline: none;
37+
border-color: #4285f4;
38+
}
39+
40+
button {
41+
padding: 0.75rem 1.5rem;
42+
background-color: #4285f4;
43+
color: white;
44+
border: none;
45+
border-radius: 4px;
46+
font-size: 1rem;
47+
font-family: inherit;
48+
cursor: pointer;
49+
transition: background-color 0.2s;
50+
}
51+
52+
button:hover {
53+
background-color: #357ae8;
54+
}
55+
56+
button:active {
57+
background-color: #2a65c8;
58+
}
59+
60+
.error-list {
61+
color: red;
62+
font-size: 0.875rem;
63+
margin: 0.25rem 0 0 0;
64+
padding-left: 0;
65+
list-style-position: inside;
66+
}
67+
68+
.error-list li {
69+
margin: 0;
70+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<form (submit)="onSubmit($event)">
2+
<div>
3+
<label>
4+
Email:
5+
<input type="email" [field]="loginForm.email" />
6+
</label>
7+
8+
@if (loginForm.email().touched() && loginForm.email().invalid()) {
9+
<ul class="error-list">
10+
@for (error of loginForm.email().errors(); track error) {
11+
<li>{{ error.message }}</li>
12+
}
13+
</ul>
14+
}
15+
</div>
16+
17+
<div>
18+
<label>
19+
Password:
20+
<input type="password" [field]="loginForm.password" />
21+
</label>
22+
23+
@if (loginForm.password().touched() && loginForm.password().invalid()) {
24+
<ul class="error-list">
25+
@for (error of loginForm.password().errors(); track error) {
26+
<li>{{ error.message }}</li>
27+
}
28+
</ul>
29+
}
30+
</div>
31+
32+
<button type="submit" [disabled]="loginForm().invalid()">Log In</button>
33+
</form>
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import {Component, signal, ChangeDetectionStrategy} from '@angular/core';
2+
import {form, Field, required, email, submit} from '@angular/forms/signals';
3+
4+
interface LoginData {
5+
email: string;
6+
password: string;
7+
}
8+
9+
@Component({
10+
selector: 'app-root',
11+
templateUrl: 'app.html',
12+
styleUrl: 'app.css',
13+
imports: [Field],
14+
changeDetection: ChangeDetectionStrategy.OnPush,
15+
})
16+
export class App {
17+
loginModel = signal<LoginData>({
18+
email: '',
19+
password: '',
20+
});
21+
22+
loginForm = form(this.loginModel, (schemaPath) => {
23+
required(schemaPath.email, {message: 'Email is required'});
24+
email(schemaPath.email, {message: 'Enter a valid email address'});
25+
26+
required(schemaPath.password, {message: 'Password is required'});
27+
});
28+
29+
onSubmit(event: Event) {
30+
event.preventDefault();
31+
submit(this.loginForm, async () => {
32+
const credentials = this.loginModel();
33+
// In a real app, this would be async:
34+
// await this.authService.login(credentials);
35+
console.log('Logging in with:', credentials);
36+
});
37+
}
38+
}

adev/src/content/examples/signal-forms/src/login-validation/app/app.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<input type="email" [field]="loginForm.email" />
66
</label>
77

8-
@if (loginForm.email().invalid()) {
8+
@if (loginForm.email().touched() && loginForm.email().invalid()) {
99
<ul class="error-list">
1010
@for (error of loginForm.email().errors(); track $index) {
1111
<li>{{ error.message }}</li>
@@ -20,7 +20,7 @@
2020
<input type="password" [field]="loginForm.password" />
2121
</label>
2222

23-
@if (loginForm.password().invalid()) {
23+
@if (loginForm.password().touched() && loginForm.password().invalid()) {
2424
<div class="error">
2525
@for (error of loginForm.password().errors(); track $index) {
2626
<p>{{ error.message }}</p>

adev/src/content/examples/signal-forms/src/login-validation/app/app.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {Component, signal, ChangeDetectionStrategy} from '@angular/core';
2-
import {form, Field, required, email, debounce} from '@angular/forms/signals';
2+
import {form, Field, required, email} from '@angular/forms/signals';
33

44
interface LoginData {
55
email: string;
@@ -20,11 +20,9 @@ export class App {
2020
});
2121

2222
loginForm = form(this.loginModel, (schemaPath) => {
23-
debounce(schemaPath.email, 500);
2423
required(schemaPath.email, {message: 'Email is required'});
2524
email(schemaPath.email, {message: 'Enter a valid email address'});
2625

27-
debounce(schemaPath.password, 500);
2826
required(schemaPath.password, {message: 'Password is required'});
2927
});
3028

adev/src/content/examples/signal-forms/src/login-validation/index.html

Lines changed: 0 additions & 14 deletions
This file was deleted.

adev/src/content/examples/signal-forms/src/login-validation/main.ts

Lines changed: 0 additions & 5 deletions
This file was deleted.

0 commit comments

Comments
 (0)