Skip to content

Commit 424ebfa

Browse files
authored
docs: add standard schema example for Angular (#1667)
* docs: add standard schema example for Angular * chore: fix Nx CI
1 parent f710cc1 commit 424ebfa

File tree

14 files changed

+465
-2
lines changed

14 files changed

+465
-2
lines changed

docs/config.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,10 @@
580580
{
581581
"label": "Form Composition",
582582
"to": "framework/angular/examples/large-form"
583+
},
584+
{
585+
"label": "Standard Schema",
586+
"to": "framework/angular/examples/standard-schema"
583587
}
584588
]
585589
},
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Editor configuration, see https://editorconfig.org
2+
root = true
3+
4+
[*]
5+
charset = utf-8
6+
indent_style = space
7+
indent_size = 2
8+
insert_final_newline = true
9+
trim_trailing_whitespace = true
10+
11+
[*.ts]
12+
quote_type = single
13+
14+
[*.md]
15+
max_line_length = off
16+
trim_trailing_whitespace = false
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# See http://help.github.com/ignore-files/ for more about ignoring files.
2+
3+
# Compiled output
4+
/dist
5+
/tmp
6+
/out-tsc
7+
/bazel-out
8+
9+
# Node
10+
/node_modules
11+
npm-debug.log
12+
yarn-error.log
13+
14+
# IDEs and editors
15+
.idea/
16+
.project
17+
.classpath
18+
.c9/
19+
*.launch
20+
.settings/
21+
*.sublime-workspace
22+
23+
# Visual Studio Code
24+
.vscode/*
25+
!.vscode/settings.json
26+
!.vscode/tasks.json
27+
!.vscode/launch.json
28+
!.vscode/extensions.json
29+
.history/*
30+
31+
# Miscellaneous
32+
/.angular/cache
33+
.sass-cache/
34+
/connect.lock
35+
/coverage
36+
/libpeerconnection.log
37+
testem.log
38+
/typings
39+
40+
# System files
41+
.DS_Store
42+
Thumbs.db
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Simple
2+
3+
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.0.1.
4+
5+
## Development server
6+
7+
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
8+
9+
## Code scaffolding
10+
11+
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
12+
13+
## Build
14+
15+
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
16+
17+
## Running unit tests
18+
19+
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
20+
21+
## Running end-to-end tests
22+
23+
Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
24+
25+
## Further help
26+
27+
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
{
2+
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3+
"version": 1,
4+
"newProjectRoot": "projects",
5+
"projects": {
6+
"simple": {
7+
"projectType": "application",
8+
"schematics": {},
9+
"root": "",
10+
"sourceRoot": "src",
11+
"prefix": "app",
12+
"architect": {
13+
"build": {
14+
"builder": "@angular-devkit/build-angular:application",
15+
"options": {
16+
"outputPath": "dist/simple",
17+
"index": "src/index.html",
18+
"browser": "src/main.ts",
19+
"polyfills": ["zone.js"],
20+
"tsConfig": "tsconfig.app.json",
21+
"assets": ["src/favicon.ico", "src/assets"],
22+
"scripts": []
23+
},
24+
"configurations": {
25+
"production": {
26+
"budgets": [
27+
{
28+
"type": "initial",
29+
"maximumWarning": "500kb",
30+
"maximumError": "1mb"
31+
},
32+
{
33+
"type": "anyComponentStyle",
34+
"maximumWarning": "2kb",
35+
"maximumError": "4kb"
36+
}
37+
],
38+
"outputHashing": "all"
39+
},
40+
"development": {
41+
"optimization": false,
42+
"extractLicenses": false,
43+
"sourceMap": true
44+
}
45+
},
46+
"defaultConfiguration": "production"
47+
},
48+
"serve": {
49+
"builder": "@angular-devkit/build-angular:dev-server",
50+
"configurations": {
51+
"production": {
52+
"buildTarget": "simple:build:production"
53+
},
54+
"development": {
55+
"buildTarget": "simple:build:development"
56+
}
57+
},
58+
"defaultConfiguration": "development"
59+
},
60+
"extract-i18n": {
61+
"builder": "@angular-devkit/build-angular:extract-i18n",
62+
"options": {
63+
"buildTarget": "simple:build"
64+
}
65+
},
66+
"test": {
67+
"builder": "@angular-devkit/build-angular:karma",
68+
"options": {
69+
"polyfills": ["zone.js", "zone.js/testing"],
70+
"tsConfig": "tsconfig.spec.json",
71+
"assets": ["src/favicon.ico", "src/assets"],
72+
"scripts": []
73+
}
74+
}
75+
}
76+
}
77+
}
78+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"name": "@tanstack/form-example-angular-standard-schema",
3+
"private": true,
4+
"type": "module",
5+
"scripts": {
6+
"ng": "ng",
7+
"start": "ng cache clean && ng serve",
8+
"build": "ng build",
9+
"watch": "ng build --watch --configuration development",
10+
"test": "ng test"
11+
},
12+
"dependencies": {
13+
"@angular/animations": "^20.0.0",
14+
"@angular/common": "^20.0.0",
15+
"@angular/compiler": "^20.0.0",
16+
"@angular/core": "^20.0.0",
17+
"@angular/forms": "^20.0.0",
18+
"@angular/platform-browser": "^20.0.0",
19+
"@angular/platform-browser-dynamic": "^20.0.0",
20+
"@angular/router": "^20.0.0",
21+
"@tanstack/angular-form": "^1.15.1",
22+
"effect": "^3.16.7",
23+
"rxjs": "^7.8.2",
24+
"tslib": "^2.8.1",
25+
"valibot": "^1.1.0",
26+
"zod": "^3.25.64",
27+
"zone.js": "0.15.1"
28+
},
29+
"devDependencies": {
30+
"@angular-devkit/build-angular": "^20.0.0",
31+
"@angular/cli": "^20.0.0",
32+
"@angular/compiler-cli": "^20.0.0",
33+
"typescript": "5.8.2"
34+
}
35+
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import { Component } from '@angular/core'
2+
import { TanStackField, injectForm, injectStore } from '@tanstack/angular-form'
3+
import * as v from 'valibot'
4+
import { z } from 'zod'
5+
import { Schema as S } from 'effect'
6+
7+
const ZodSchema = z.object({
8+
firstName: z
9+
.string()
10+
.min(3, '[Zod] You must have a length of at least 3')
11+
.startsWith('A', "[Zod] First name must start with 'A'"),
12+
lastName: z.string().min(3, '[Zod] You must have a length of at least 3'),
13+
})
14+
15+
const ValibotSchema = v.object({
16+
firstName: v.pipe(
17+
v.string(),
18+
v.minLength(3, '[Valibot] You must have a length of at least 3'),
19+
v.startsWith('A', "[Valibot] First name must start with 'A'"),
20+
),
21+
lastName: v.pipe(
22+
v.string(),
23+
v.minLength(3, '[Valibot] You must have a length of at least 3'),
24+
),
25+
})
26+
27+
const EffectSchema = S.standardSchemaV1(
28+
S.Struct({
29+
firstName: S.String.pipe(
30+
S.minLength(3),
31+
S.annotations({
32+
message: () => '[Effect/Schema] You must have a length of at least 3',
33+
}),
34+
),
35+
lastName: S.String.pipe(
36+
S.minLength(3),
37+
S.annotations({
38+
message: () => '[Effect/Schema] You must have a length of at least 3',
39+
}),
40+
),
41+
}),
42+
)
43+
44+
@Component({
45+
selector: 'app-root',
46+
standalone: true,
47+
imports: [TanStackField],
48+
template: `
49+
<form (submit)="handleSubmit($event)">
50+
<div>
51+
<ng-container
52+
[tanstackField]="form"
53+
name="firstName"
54+
#firstName="field"
55+
>
56+
<label [for]="firstName.api.name">First Name:</label>
57+
<input
58+
[id]="firstName.api.name"
59+
[name]="firstName.api.name"
60+
[value]="firstName.api.state.value"
61+
(blur)="firstName.api.handleBlur()"
62+
(input)="firstName.api.handleChange($any($event).target.value)"
63+
/>
64+
@if (firstName.api.state.meta.isTouched) {
65+
@for (error of firstName.api.state.meta.errors; track $index) {
66+
<div style="color: red">
67+
{{ error.message }}
68+
</div>
69+
}
70+
}
71+
@if (firstName.api.state.meta.isValidating) {
72+
<p>Validating...</p>
73+
}
74+
</ng-container>
75+
</div>
76+
<div>
77+
<ng-container [tanstackField]="form" name="lastName" #lastName="field">
78+
<label [for]="lastName.api.name">Last Name:</label>
79+
<input
80+
[id]="lastName.api.name"
81+
[name]="lastName.api.name"
82+
[value]="lastName.api.state.value"
83+
(blur)="lastName.api.handleBlur()"
84+
(input)="lastName.api.handleChange($any($event).target.value)"
85+
/>
86+
@if (firstName.api.state.meta.isTouched) {
87+
@for (error of firstName.api.state.meta.errors; track $index) {
88+
<div style="color: red">
89+
{{ error.message }}
90+
</div>
91+
}
92+
}
93+
@if (firstName.api.state.meta.isValidating) {
94+
<p>Validating...</p>
95+
}
96+
</ng-container>
97+
</div>
98+
<button type="submit" [disabled]="!canSubmit()">
99+
{{ isSubmitting() ? '...' : 'Submit' }}
100+
</button>
101+
<button type="reset" (click)="form.reset()">Reset</button>
102+
</form>
103+
`,
104+
})
105+
export class AppComponent {
106+
form = injectForm({
107+
defaultValues: {
108+
firstName: '',
109+
lastName: '',
110+
},
111+
112+
validators: {
113+
// DEMO: You can switch between schemas seamlessly
114+
onChange: ZodSchema,
115+
// onChange: ValibotSchema,
116+
// onChange: EffectSchema,
117+
},
118+
onSubmit({ value }) {
119+
// Do something with form data
120+
console.log(value)
121+
},
122+
})
123+
124+
canSubmit = injectStore(this.form, (state) => state.canSubmit)
125+
isSubmitting = injectStore(this.form, (state) => state.isSubmitting)
126+
127+
handleSubmit(event: SubmitEvent) {
128+
event.preventDefault()
129+
event.stopPropagation()
130+
this.form.handleSubmit()
131+
}
132+
}
14.7 KB
Binary file not shown.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<title>Simple</title>
6+
<base href="/" />
7+
<meta name="viewport" content="width=device-width, initial-scale=1" />
8+
<link rel="icon" type="image/x-icon" href="favicon.ico" />
9+
</head>
10+
<body>
11+
<app-root></app-root>
12+
</body>
13+
</html>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { bootstrapApplication } from '@angular/platform-browser'
2+
import { AppComponent } from './app/app.component'
3+
4+
bootstrapApplication(AppComponent).catch((err) => console.error(err))

0 commit comments

Comments
 (0)