From ccb0264edbb5a8cf38ff9af91e87f21ae75523a9 Mon Sep 17 00:00:00 2001 From: Ben Hong Date: Wed, 19 Nov 2025 09:35:23 -0500 Subject: [PATCH] docs: add signal forms playground template (cherry picked from commit e0eb619c0328d7055db1eacdd1aebe8b936f2d02) --- .../playground/4-signal-forms/config.json | 5 ++ .../playground/4-signal-forms/src/main.css | 70 +++++++++++++++++ .../playground/4-signal-forms/src/main.ts | 77 +++++++++++++++++++ 3 files changed, 152 insertions(+) create mode 100644 adev/src/content/tutorials/playground/4-signal-forms/config.json create mode 100644 adev/src/content/tutorials/playground/4-signal-forms/src/main.css create mode 100644 adev/src/content/tutorials/playground/4-signal-forms/src/main.ts diff --git a/adev/src/content/tutorials/playground/4-signal-forms/config.json b/adev/src/content/tutorials/playground/4-signal-forms/config.json new file mode 100644 index 00000000000..58beee91230 --- /dev/null +++ b/adev/src/content/tutorials/playground/4-signal-forms/config.json @@ -0,0 +1,5 @@ +{ + "type": "editor-only", + "title": "Signals forms template", + "openFiles": ["src/main.ts", "src/main.css"] +} diff --git a/adev/src/content/tutorials/playground/4-signal-forms/src/main.css b/adev/src/content/tutorials/playground/4-signal-forms/src/main.css new file mode 100644 index 00000000000..2b9a3100a69 --- /dev/null +++ b/adev/src/content/tutorials/playground/4-signal-forms/src/main.css @@ -0,0 +1,70 @@ +form { + display: flex; + flex-direction: column; + gap: 1rem; + max-width: 400px; + padding: 1rem; + font-family: + Inter, + system-ui, + -apple-system, + sans-serif; +} + +div { + display: flex; + flex-direction: column; + gap: 0.25rem; +} + +label { + display: flex; + flex-direction: column; + gap: 0.25rem; + font-weight: 500; +} + +input { + padding: 0.5rem; + border: 1px solid #ccc; + border-radius: 4px; + font-size: 1rem; + font-family: inherit; +} + +input:focus { + outline: none; + border-color: #4285f4; +} + +button { + padding: 0.75rem 1.5rem; + background-color: #4285f4; + color: white; + border: none; + border-radius: 4px; + font-size: 1rem; + font-family: inherit; + cursor: pointer; + transition: background-color 0.2s; +} + +button:hover { + background-color: #357ae8; +} + +button:active { + background-color: #2a65c8; +} + +.error-list { + color: red; + font-size: 0.875rem; + margin: 0.25rem 0 0 0; + padding-left: 0; + list-style-position: inside; +} + +.error-list p { + margin: 0; +} diff --git a/adev/src/content/tutorials/playground/4-signal-forms/src/main.ts b/adev/src/content/tutorials/playground/4-signal-forms/src/main.ts new file mode 100644 index 00000000000..928d6ef0cee --- /dev/null +++ b/adev/src/content/tutorials/playground/4-signal-forms/src/main.ts @@ -0,0 +1,77 @@ +import {bootstrapApplication} from '@angular/platform-browser'; +import {Component, signal, ChangeDetectionStrategy} from '@angular/core'; +// @ts-ignore There's a known issue with type support in the playground editor for signal forms +import {form, Field, required, email, debounce, submit} from '@angular/forms/signals'; + +interface LoginData { + email: string; + password: string; +} + +@Component({ + selector: 'app-root', + template: ` +
+
+ + + @if (loginForm.email().invalid()) { +
    + @for (error of loginForm.email().errors(); track $index) { +
  • {{ error.message }}
  • + } +
+ } +
+ +
+ + + @if (loginForm.password().invalid()) { +
+ @for (error of loginForm.password().errors(); track $index) { +

{{ error.message }}

+ } +
+ } +
+ + +
+ `, + styleUrl: 'main.css', + imports: [Field], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class LoginApp { + loginModel = signal({ + email: '', + password: '', + }); + + loginForm = form(this.loginModel, (schemaPath) => { + debounce(schemaPath.email, 500); + required(schemaPath.email, {message: 'Email is required'}); + email(schemaPath.email, {message: 'Enter a valid email address'}); + debounce(schemaPath.password, 500); + required(schemaPath.password, {message: 'Password is required'}); + }); + + onSubmit(event: Event) { + event.preventDefault(); + submit(this.loginForm, async () => { + // Perform login logic here + const credentials = this.loginModel(); + console.log('Logging in with:', credentials); + // e.g., await this.authService.login(credentials); + }); + } +} + +bootstrapApplication(LoginApp);