Skip to content

Commit fa3810f

Browse files
authored
Merge pull request #113 from sonusindhu/signal-forms
Signal forms - example 2
2 parents f0a8c7a + 762d057 commit fa3810f

File tree

9 files changed

+218
-1
lines changed

9 files changed

+218
-1
lines changed

src/app/examples/signal-form/example1/example1.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<div class="flex flex-wrap gap-2 items-center justify-start mt-4 mb-4">
33
<button mat-stroked-button color="primary" routerLink="/signal-forms">Go to List</button>
44
<button mat-stroked-button color="primary" disabled>Prev</button>
5-
<button mat-stroked-button color="primary" disabled="">Next</button>
5+
<button mat-stroked-button color="primary" routerLink="/signal-forms/example2">Next</button>
66
</div>
77

88
<mat-tab-group>
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<div class="example-container">
2+
<div class="flex flex-wrap gap-2 items-center justify-start mt-4 mb-4">
3+
<button mat-stroked-button color="primary" routerLink="/signal-forms">Go to List</button>
4+
<button mat-stroked-button color="primary" routerLink="/signal-forms/example1">Prev</button>
5+
<button mat-stroked-button color="primary" disabled="">Next</button>
6+
</div>
7+
8+
<mat-tab-group>
9+
<mat-tab label="Demo">
10+
<div class="content-area">
11+
<h2>Example 2: Signal Form Validations</h2>
12+
<p>This example demonstrates the use of Angular signals for managing form errors.</p>
13+
</div>
14+
15+
<div class="demo-section">
16+
<mat-card>
17+
<mat-card-content>
18+
19+
20+
<form>
21+
<div>
22+
<input type="text" placeholder="Enter name" [control]="userForm.name" class="w-full p-2 border border-gray-300 rounded-md text-base mb-4" />
23+
@if(userForm.name().touched() || userForm.name().dirty()){
24+
@for (item of userForm.name().errors(); track item) {
25+
<p class="text-red-500 p-0">{{ item.message }}</p>
26+
}
27+
}
28+
</div>
29+
30+
<div>
31+
<input type="email" placeholder="Enter email" [control]="userForm.email"
32+
class="w-full p-2 border border-gray-300 rounded-md text-base mb-4" />
33+
@if(userForm.email().touched() || userForm.email().dirty()){
34+
@for (item of userForm.email().errors(); track $index) {
35+
<p class="text-red-500 p-0">{{ item.message }}</p>
36+
}
37+
}
38+
</div>
39+
</form>
40+
41+
42+
</mat-card-content>
43+
</mat-card>
44+
45+
<h3>Key Features</h3>
46+
<mat-card>
47+
<mat-card-content>
48+
<ul>
49+
<li>⚡ Reactive form state with Angular signals</li>
50+
<li>✅ Built-in validation and error display</li>
51+
<li>🔄 Real-time UI updates on input changes</li>
52+
<li>🧩 Minimal, readable form logic</li>
53+
<li>🛠️ Easily extensible for more fields and rules</li>
54+
</ul>
55+
<p>This example demonstrates efficient form state management and validation using Angular signals, with instant error feedback and a clean, extensible approach.</p>
56+
</mat-card-content>
57+
</mat-card>
58+
59+
</div>
60+
</mat-tab>
61+
62+
<mat-tab label="HTML">
63+
<markdown clipboard [src]="'assets/examples/signal-forms/example2/example2.component.html.md'"></markdown>
64+
</mat-tab>
65+
<mat-tab label="TS">
66+
<markdown clipboard [src]="'assets/examples/signal-forms/example2/example2.component.ts.md'"></markdown>
67+
</mat-tab>
68+
<mat-tab label="SCSS">
69+
<markdown clipboard [src]="'assets/examples/signal-forms/example2/example2.component.scss.md'"></markdown>
70+
</mat-tab>
71+
</mat-tab-group>
72+
</div>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
:host {
2+
display: contents;
3+
.demo-section {
4+
margin-top: 2rem;
5+
display: flex;
6+
flex-direction: column;
7+
gap: 2rem;
8+
align-items: center;
9+
width: 100%;
10+
margin-left: auto;
11+
margin-right: auto;
12+
padding-bottom: 2rem;
13+
}
14+
.demo-section mat-card {
15+
width: 100%;
16+
max-width: 500px;
17+
box-shadow: 0 2px 8px rgba(0,0,0,0.07);
18+
border-radius: 8px;
19+
}
20+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { Component, effect, signal } from '@angular/core';
2+
import { MatButtonModule } from '@angular/material/button';
3+
import { MatTabsModule } from '@angular/material/tabs';
4+
import { MatCardModule } from '@angular/material/card';
5+
import { MatIconModule } from '@angular/material/icon';
6+
import { MarkdownComponent } from 'ngx-markdown';
7+
import { RouterModule } from '@angular/router';
8+
9+
import { form, Control, required, pattern, email, schema } from '@angular/forms/signals';
10+
import { JsonPipe } from '@angular/common';
11+
12+
13+
const formSchema = schema<{name: string, email: string}>((form) => {
14+
required(form.name, { message: 'please enter your name' });
15+
pattern(form.name, /^[a-z ]+$/i, { message: 'Enter valid name' });
16+
17+
18+
required(form.email, { message: 'please enter your email' });
19+
email(form.email, { message: 'Enter valid email' });
20+
});
21+
22+
@Component({
23+
selector: 'app-form-example1',
24+
imports: [
25+
MatButtonModule,
26+
MatTabsModule,
27+
MatCardModule,
28+
MatIconModule,
29+
MarkdownComponent,
30+
RouterModule,
31+
Control,
32+
JsonPipe,
33+
],
34+
templateUrl: './example2.component.html',
35+
styleUrl: './example2.component.scss'
36+
})
37+
export class FormExample2Component {
38+
39+
// create a signal for the user form
40+
public user = signal({ name: '', email: '' });
41+
42+
public errors = effect(() => {
43+
console.log('errors - ', this.userForm().errors())
44+
return this.userForm().errors();
45+
});
46+
47+
// create a form using the user signal
48+
public userForm = form(this.user, formSchema);
49+
50+
}

src/app/examples/signal-form/signal-forms-routings.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ const ADVANCED_ROUTES: Route[] = [
99
{
1010
path: 'example1',
1111
loadComponent: () => import('./example1/example1.component').then(x => x.FormExample1Component),
12+
},
13+
{
14+
path: 'example2',
15+
loadComponent: () => import('./example2/example2.component').then(x => x.FormExample2Component),
1216
}
1317
];
1418

src/app/examples/signal-form/signal-forms.const.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,10 @@ export const SIGNAL_FORM_EXAMPLES: ExampleModel[] = [
55
title: 'Example 1: Basic Signal Form',
66
content: 'Demonstrates the use of Angular signals for managing form state.',
77
routerLink: '/signal-forms/example1'
8+
},
9+
{
10+
title: 'Example 2: Signal Form Validations',
11+
content: 'Showcases validation, error handling, and extensible form logic using Angular signals.',
12+
routerLink: '/signal-forms/example2'
813
}
914
];
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
```html
2+
<form>
3+
<div>
4+
<input type="text" placeholder="Enter name" [control]="userForm.name" class="w-full p-2 border border-gray-300 rounded-md text-base mb-4" />
5+
@if(userForm.name().touched() || userForm.name().dirty()){
6+
@for (item of userForm.name().errors(); track item) {
7+
<p class="text-red-500 p-0">{{ item.message }}</p>
8+
}
9+
}
10+
</div>
11+
<div>
12+
<input type="email" placeholder="Enter email" [control]="userForm.email" class="w-full p-2 border border-gray-300 rounded-md text-base mb-4" />
13+
@if(userForm.email().touched() || userForm.email().dirty()){
14+
@for (item of userForm.email().errors(); track $index) {
15+
<p class="text-red-500 p-0">{{ item.message }}</p>
16+
}
17+
}
18+
</div>
19+
</form>
20+
```
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
```scss
2+
:host {
3+
display: contents;
4+
.demo-section {
5+
margin-top: 2rem;
6+
display: flex;
7+
flex-direction: column;
8+
gap: 2rem;
9+
align-items: center;
10+
width: 100%;
11+
margin-left: auto;
12+
margin-right: auto;
13+
padding-bottom: 2rem;
14+
}
15+
.demo-section mat-card {
16+
width: 100%;
17+
max-width: 500px;
18+
box-shadow: 0 2px 8px rgba(0,0,0,0.07);
19+
border-radius: 8px;
20+
}
21+
}
22+
```
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
```ts
2+
import { Component, effect, signal } from '@angular/core';
3+
import { form, Control, required, pattern, email, schema } from '@angular/forms/signals';
4+
5+
const formSchema = schema<{name: string, email: string}>((form) => {
6+
required(form.name, { message: 'please enter your name' });
7+
pattern(form.name, /^[a-z ]+$/i, { message: 'Enter valid name' });
8+
required(form.email, { message: 'please enter your email' });
9+
email(form.email, { message: 'Enter valid email' });
10+
});
11+
12+
@Component({
13+
selector: 'app-form-example2',
14+
templateUrl: './example2.component.html',
15+
styleUrl: './example2.component.scss'
16+
})
17+
export class FormExample2Component {
18+
public user = signal({ name: '', email: '' });
19+
public errors = effect(() => {
20+
console.log('errors - ', this.userForm().errors())
21+
return this.userForm().errors();
22+
});
23+
public userForm = form(this.user, formSchema);
24+
}

0 commit comments

Comments
 (0)