1- import {
2- ChangeDetectionStrategy ,
3- Component ,
4- computed ,
5- output ,
6- signal ,
7- } from '@angular/core' ;
1+ import { ChangeDetectionStrategy , Component , output } from '@angular/core' ;
2+ import { FormGroup , ReactiveFormsModule } from '@angular/forms' ;
3+ import { MatFormField , MatLabel , MatError } from '@angular/material/form-field' ;
4+ import { MatInput } from '@angular/material/input' ;
85import { MatButton } from '@angular/material/button' ;
96import { MatIcon } from '@angular/material/icon' ;
10- import { FormField , form , required , submit } from '@angular/forms/signals' ;
7+ import { SignalFormControl } from '@angular/forms/signals/compat' ;
8+ import { required } from '@angular/forms/signals' ;
119
1210import { CreateTodoRequest } from '../../models/todo' ;
1311
14- interface TodoModel {
15- title : string ;
16- description : string ;
17- }
18-
1912@Component ( {
2013 selector : 'lib-todo-form' ,
21- imports : [ MatButton , MatIcon , FormField ] ,
14+ imports : [
15+ ReactiveFormsModule ,
16+ MatFormField ,
17+ MatLabel ,
18+ MatError ,
19+ MatInput ,
20+ MatButton ,
21+ MatIcon ,
22+ ] ,
2223 template : `
2324 <form
24- class="flex flex-col gap-4 rounded-2xl bg-surface-container p-4 sm:flex-row sm:items-end"
25- (submit)="handleSubmit($event)"
25+ class="flex flex-col gap-4 rounded-2xl bg-surface-container p-4 sm:flex-row sm:items-start"
26+ [formGroup]="form"
27+ (ngSubmit)="submit()"
2628 >
27- <div class="flex flex-1 flex-col gap-1.5">
28- <label
29- class="text-xs font-semibold text-on-surface-variant"
30- for="todo-title"
31- >
32- Title
33- </label>
29+ <mat-form-field
30+ appearance="outline"
31+ subscriptSizing="dynamic"
32+ class="flex-1"
33+ >
34+ <mat-label>Title</mat-label>
3435 <input
35- id="todo-title"
36- [formField]="todoForm. title"
36+ matInput
37+ formControlName=" title"
3738 placeholder="What needs to be done?"
3839 autocomplete="off"
39- class="h-14 rounded-lg border bg-surface px-3 text-sm text-on-surface placeholder:text-on-surface-variant/50 outline-none transition-colors focus:ring-2 focus:ring-primary"
40- [class.border-outline]="
41- !(todoForm.title().touched() && !todoForm.title().valid())
42- "
43- [class.border-error]="
44- todoForm.title().touched() && !todoForm.title().valid()
45- "
4640 />
47- @if (todoForm.title().touched() && todoForm.title().errors().length) {
48- <p class="text-xs text-error">
49- {{ todoForm.title().errors()[0].message }}
50- </p>
51- }
52- </div>
53- <div class="flex flex-1 flex-col gap-1.5">
54- <label
55- class="text-xs font-semibold text-on-surface-variant"
56- for="todo-description"
57- >
58- Description
59- </label>
41+ <mat-error>Title is required</mat-error>
42+ </mat-form-field>
43+ <mat-form-field
44+ appearance="outline"
45+ subscriptSizing="dynamic"
46+ class="flex-1"
47+ >
48+ <mat-label>Description</mat-label>
6049 <input
61- id="todo-description"
62- [formField]="todoForm. description"
50+ matInput
51+ formControlName=" description"
6352 placeholder="Optional details…"
6453 autocomplete="off"
65- class="h-14 rounded-lg border border-outline bg-surface px-3 text-sm text-on-surface placeholder:text-on-surface-variant/50 outline-none transition-colors focus:ring-2 focus:ring-primary"
6654 />
67- </div >
55+ </mat-form-field >
6856 <button
6957 mat-flat-button
7058 type="submit"
7159 class="h-14 shrink-0"
72- [disabled]="!formValid() "
60+ [disabled]="form.invalid "
7361 >
7462 <mat-icon>add</mat-icon>
7563 Add
@@ -85,22 +73,23 @@ interface TodoModel {
8573export class TodoForm {
8674 readonly create = output < CreateTodoRequest > ( ) ;
8775
88- readonly model = signal < TodoModel > ( { title : '' , description : '' } ) ;
89- readonly todoForm = form ( this . model , ( s ) => {
90- required ( s . title , { message : 'Title is required' } ) ;
76+ readonly form = new FormGroup ( {
77+ title : new SignalFormControl ( '' , ( s ) => {
78+ required ( s , { message : 'Title is required' } ) ;
79+ } ) ,
80+ description : new SignalFormControl ( '' ) ,
9181 } ) ;
92- readonly formValid = computed ( ( ) => this . todoForm . title ( ) . valid ( ) ) ;
9382
94- handleSubmit ( event : SubmitEvent ) : void {
95- event . preventDefault ( ) ;
96- submit ( this . todoForm , ( field ) => {
97- this . create . emit ( {
98- title : field . title ( ) . value ( ) . trim ( ) ,
99- description : field . description ( ) . value ( ) . trim ( ) ,
100- completed : false ,
101- } ) ;
102- this . model . set ( { title : '' , description : '' } ) ;
103- return Promise . resolve ( ) ;
83+ submit ( ) {
84+ if ( this . form . invalid ) {
85+ this . form . markAllAsTouched ( ) ;
86+ return ;
87+ }
88+ this . create . emit ( {
89+ title : ( this . form . value . title ?? '' ) . trim ( ) ,
90+ description : ( this . form . value . description ?? '' ) . trim ( ) ,
91+ completed : false ,
10492 } ) ;
93+ this . form . reset ( { title : '' , description : '' } ) ;
10594 }
10695}
0 commit comments