Skip to content

Commit 77ce141

Browse files
fix: restore mat-label using SignalFormControl from signals/compat
Use SignalFormControl from @angular/forms/signals/compat so the todo form keeps signal-based validation rules while integrating with mat-form-field / mat-label / mat-error via standard ReactiveFormsModule formControlName binding. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 3af1288 commit 77ce141

1 file changed

Lines changed: 53 additions & 64 deletions

File tree

Lines changed: 53 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,63 @@
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';
85
import { MatButton } from '@angular/material/button';
96
import { 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

1210
import { 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 {
8573
export 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

Comments
 (0)