Angular Signal Forms 如何在 Material 中显示服务端错误?
可以用 validate() 将 errorResponse 中的错误接入 Signal Forms,再用 Angular Material 的 ErrorStateMatcher 控制红色错误状态,并在 <mat-error> 中显示错误消息。
题目中的 errorResponse 是一个独立的 signal。声明式校验可以直接响应服务端错误的变化,不必在 effect() 中逐个写入字段错误。
将服务端错误注册为字段校验结果
validate() 接收字段的 schema 路径和校验函数。让校验函数读取 errorResponse(),再将错误转换为带有 kind 和 message 的对象。该 API 用于声明字段的校验逻辑,详见 Angular v21 的 validate API。
保留现有的 errorResponse、ticketModel 和接口定义,删除原来的 fieldErrors = effect(...),将表单初始化改为:
import { form, FormField, validate } from '@angular/forms/signals';
// 以下成员放在原组件类中,位于 errorResponse、ticketModel 之后。
private serverErrors(propertyName: string) {
return this.errorResponse().errors
.filter(error => error.propertyName === propertyName)
.map(error => ({
kind: 'server',
message: error.errorMessage,
}));
}
ticketForm = form(this.ticketModel, path => {
validate(path.title, () => this.serverErrors('Title'));
validate(path.repro, () => this.serverErrors('Repro'));
});
代码中显式处理了字段名映射:服务端使用 Title、Repro,客户端模型使用 title、repro。有多个同名字段错误时,返回的数组会保留全部消息;没有匹配错误时返回空数组。
接入后,可以通过以下状态检查映射结果:
this.ticketForm.title().errors();
this.ticketForm.title().invalid();
this.ticketForm.repro().errors();
this.ticketForm.repro().invalid();
让 Material 根据字段状态显示红框
字段无效后,界面不一定马上显示红框。Angular Material 使用 ErrorStateMatcher 决定是否进入错误状态,默认显示策略还与用户交互或表单提交有关。可以通过 errorStateMatcher 自定义显示条件,参见 Material Input 的错误显示说明。
题目中的错误在初始化时已存在。如果需要立即显示,可以让每个 matcher 直接读取对应字段的 invalid():
import { ErrorStateMatcher } from '@angular/material/core';
// 放在组件类中,位于 ticketForm 初始化之后。
readonly titleErrorMatcher: ErrorStateMatcher = {
isErrorState: () => this.ticketForm.title().invalid(),
};
readonly reproErrorMatcher: ErrorStateMatcher = {
isErrorState: () => this.ticketForm.repro().invalid(),
};
这里的 isErrorState 读取闭包中的字段状态,不依赖 matcher 接收的传统 FormControl 参数。
题目使用的是 21.1 系列。为避免显示刷新依赖具体版本的表单互操作实现,可以增加一个小指令,显式调用 MatInput.updateErrorState(),重新计算输入框的错误状态。参见 MatInput 源码。
import { Directive, DoCheck, inject } from '@angular/core';
import { MatInput } from '@angular/material/input';
@Directive({
selector: 'input[matInput][refreshSignalErrorState]',
standalone: true,
})
export class RefreshSignalErrorState implements DoCheck {
private readonly matInput = inject(MatInput, { self: true });
ngDoCheck(): void {
this.matInput.updateErrorState();
}
}
这个指令负责刷新显示状态,校验结果仍由 validate() 提供。如果实际安装的版本已经能自动刷新 matcher,可以省略该指令。
在模板中显示错误消息
组件需要导入 MatError 和上面定义的兼容指令:
import {
MatError,
MatFormField,
MatLabel,
} from '@angular/material/form-field';
// 更新原 @Component 装饰器的 imports:
imports: [
MatFormField,
MatLabel,
MatError,
MatInput,
FormField,
RefreshSignalErrorState,
],
将模板改为:
<form novalidate>
<mat-form-field appearance="outline">
<mat-label>Ticket</mat-label>
<input
matInput
refreshSignalErrorState
[formField]="ticketForm.title"
[errorStateMatcher]="titleErrorMatcher"
>
@for (error of ticketForm.title().errors(); track $index) {
<mat-error>{{ error.message }}</mat-error>
}
</mat-form-field>
<br/>
<mat-form-field appearance="outline">
<mat-label>Repro</mat-label>
<input
matInput
refreshSignalErrorState
[formField]="ticketForm.repro"
[errorStateMatcher]="reproErrorMatcher"
>
@for (error of ticketForm.repro().errors(); track $index) {
<mat-error>{{ error.message }}</mat-error>
}
</mat-form-field>
</form>
[formField] 绑定字段,matcher 决定 Material 是否进入错误状态,<mat-error> 展示消息。只添加错误文本不会自动创建字段校验结果。
明确服务端错误何时清除
上述校验器依赖 errorResponse()。用户修改输入值后,旧的服务端错误仍然存在,直到应用更新或清除相应错误。
如果产品要求用户编辑某个字段时清除该字段的服务端错误,可以在组件中加入:
clearServerErrors(propertyName: string): void {
this.errorResponse.update(response => ({
...response,
errors: response.errors.filter(
error => error.propertyName !== propertyName,
),
}));
}
然后分别在对应输入框上增加:
(input)="clearServerErrors('Title')"
(input)="clearServerErrors('Repro')"
清除错误只表示旧错误不再用于当前输入,最终结果仍需在下次请求时由服务端校验。真实请求返回后,通过 this.errorResponse.set(response) 更新错误即可。
对于无法映射到字段的错误,应在表单级区域显示,不要直接丢弃,也不要通过类型断言把任意服务端属性名当作表单字段。
^21.1.0 和 ^21.1.3 允许安装一定范围内的更新,不能确定实际安装版本。Angular v21 文档仍将 Signal Forms 标记为实验性 API。排查兼容问题时,应以 lockfile 和实际安装包为准,不能直接假定最新文档中的接口都适用于 21.1。参见 Angular v21 的 FormField API。
备注:内容仅供参考。
