Angular Signal Forms 自定义控件开发指南:控制接口、状态绑定与值转换的完整实现
导读:浏览器自带的
input、select、textarea只能覆盖常见输入场景,日期选择器、富文本编辑器、自动补全标签选择器等特殊控件必须由应用自行实现。本文基于 Angular 仓库中 Signal Forms(信号表单)的自定义控件官方指南,讲解如何通过FormValueControl/FormCheckboxControl控制接口与[formField]指令,让任何组件无缝接入表单的值绑定、校验与状态管理,并深入剖析其源码实现。读完本文,你将能够实现最小可用控件、接入 touched/disabled/errors 等状态信号、处理 UI 值与模型值的转换(parse/format)与解析错误,并打造可跨表单复用的控件配套 schema。
前置知识:Signal Forms 的控件接入模型
本文假设你已经熟悉 Signal Forms 基础概念。Signal Forms 与「模板驱动表单 / 响应式表单」有一个本质区别:接入表单系统的不是一个装饰器或访问器注册表,而是一组纯 TypeScript 接口契约(control interface)。
只要组件实现了特定的控制接口,并用 [formField] 指令把它绑定到一个字段(FieldTree)上,该指令就会自动完成三件事:双向值绑定、表单状态(disabled、errors、touched 等)同步、以及校验约束值透传。对 Signal Forms 而言,内置控件与自定义控件是「同一类公民」——指令不区分二者,校验、状态更新与数据绑定对两者完全一致。
一个非常实用的兼容性结论(在原文档中有明确说明):Signal Forms 自定义控件可以在不做任何额外兼容代码的前提下,与 Signal、Reactive(响应式)和 Template-Driven(模板驱动)三种表单混用,详见 Migration 指南 中关于自定义控件的章节。
创建最基础的自定义控件
先从一个尽量精简的实现入手,逐步添加能力。
最小文本输入控件:FormValueControl
文本、数字、日期选择、下拉选择等「编辑单个值」的控件,只需要实现 FormValueControl 接口并提供一个必需的 value model 信号:
import {Component, model} from '@angular/core';
import {FormValueControl} from '@angular/forms/signals';
@Component({
selector: 'app-basic-input',
template: `
<div class="basic-input">
<input
type="text"
[value]="value()"
(input)="value.set($event.target.value)"
placeholder="Enter text..."
/>
</div>
`,
})
export class BasicInput implements FormValueControl<string> {
/** The current input value */
value = model('');
}
实现要点:
- 模板中用
[value]="value()"把内部<input>渲染值绑定到组件的valuemodel 信号上; - 原生
(input)事件触发时调用value.set(...),把用户输入写回 model 信号; value = model('')是 Angular 的 model 信号(双向绑定原语),外部通过[formField]与它同步,双方任一方的写入都会通过信号响应自动传播到另一方。
最小开关/复选框控件:FormCheckboxControl
「布尔开/关」类的控件(开关、切换按钮、复选框)需要:
- 实现
FormCheckboxControl接口,让[formField]指令能识别它为表单控件; - 提供一个
checkedmodel 信号。
import {Component, model, ChangeDetectionStrategy} from '@angular/core';
import {FormCheckboxControl} from '@angular/forms/signals';
@Component({
selector: 'app-basic-toggle',
template: `
<button type="button" [class.active]="checked()" (click)="toggle()">
<span class="toggle-slider"></span>
</button>
`,
})
export class BasicToggle implements FormCheckboxControl {
/** Whether the toggle is checked */
checked = model<boolean>(false);
toggle() {
this.checked.update((val) => !val);
}
}
与 FormValueControl 的差异只在于「用哪个信号承载值」:布尔控件用 checked,普通输入控件用 value。
在表单中使用自定义控件
控件写好后,凡是内置输入框能出现的位置,它都能出现——只需为它加上 [formField] 指令:
import {Component, signal, ChangeDetectionStrategy} from '@angular/core';
import {form, FormField, required} from '@angular/forms/signals';
import {BasicInput} from './basic-input';
import {BasicToggle} from './basic-toggle';
@Component({
imports: [FormField, BasicInput, BasicToggle],
template: `
<form novalidate>
<label>
Email
<app-basic-input [formField]="registrationForm.email" />
</label>
<label>
Accept terms
<app-basic-toggle [formField]="registrationForm.acceptTerms" />
</label>
<button type="submit" [disabled]="registrationForm().invalid()">Register</button>
</form>
`,
})
export class Registration {
registrationModel = signal({
email: '',
acceptTerms: false,
});
registrationForm = form(this.registrationModel, (schemaPath) => {
required(schemaPath.email, {message: 'Email is required'});
required(schemaPath.acceptTerms, {message: 'You must accept the terms'});
});
}
值得注意:form() 的 schema 回调参数(此处命名为 schemaPath)是一个 SchemaPathTree 对象,为表单中所有字段提供访问路径,参数名可以任意起。示例中的按钮禁用逻辑 registrationForm().invalid() 由表单整体校验状态驱动——自定义控件与内置输入在其中的地位完全等价。
深入理解控制接口契约
前两个组件之所以能被识别,是因为它们实现了不同的控制接口,这些接口充当「Signal Forms 与控件通信的协议」。
FormValueControl:单值输入控件
适用于文本输入、数字输入、日期选择、下拉选择等一切「编辑单个值」的控件。实现该接口:
- 必需属性:提供一个
valuemodel 信号; [formField]指令行为:把表单字段的值绑定到该value信号上。
IMPORTANT:实现
FormValueControl的控件不得再定义checked属性。
FormCheckboxControl:布尔开关类控件
适用于开关、切换、复选框等「表示布尔开/关状态」的控件。实现该接口:
- 必需属性:提供一个
checkedmodel 信号; [formField]指令行为:把表单字段的值绑定到该checked信号上。
IMPORTANT:实现
FormCheckboxControl的控件不得再定义value属性。
从源码层面看,这条「互斥」约束被 TypeScript 严格编码在接口里:FormValueControl 声明 readonly checked?: undefined,FormCheckboxControl 声明 readonly value?: undefined,见 api/control.ts。源码注释还透露,之所以把 value/checked 互斥写死,是因为未来可能引入第三种「同时定义 value 与 checked」的单选按钮(radio)控件契约,目前仍在评估中——这说明接口设计为演进留了余地。
基接口 FormUiControl 与可选状态属性
FormValueControl 与 FormCheckboxControl 都继承自 FormUiControl(源码见 api/control.ts),后者提供了与表单状态集成的一系列可选属性。所有属性均为可选,只实现你的控件真正需要的即可。
FormUiControl 中的属性类型均为 InputSignal(允许 InputSignalWithTransform),意味着指令会把对应状态「推送」给你的输入信号。可选项按用途分为四类:
交互状态(interaction state):追踪用户是否操作过控件
| Property | Purpose |
|---|---|
touched |
用户是否与该字段发生过交互 |
dirty |
当前值是否与初始值不同 |
touch(输出) |
输出事件,标记字段已被触及(详见下文 debounce('blur') 一节) |
校验状态(validation state):展示校验反馈
| Property | Purpose |
|---|---|
errors |
当前校验错误数组 |
valid |
字段是否有效 |
invalid |
字段是否存在校验错误 |
pending |
异步校验是否进行中 |
可用性状态(availability state):控制用户能否操作字段
| Property | Purpose |
|---|---|
disabled |
字段是否被禁用 |
disabledReasons |
字段被禁用的原因列表 |
readonly |
字段是否只读(可见但不可编辑) |
hidden |
字段是否从视图中隐藏 |
disabledReasons是一个DisabledReason对象数组。每个对象包含一个指向字段树的field引用属性,以及可选的message属性;读取提示文案使用reason.message。
校验约束(validation constraints):从表单接收约束值
| Property | Purpose |
|---|---|
required |
字段是否为必填 |
min |
最小数值(无约束时为 undefined) |
max |
最大数值(无约束时为 undefined) |
minLength |
最小字符串长度(无约束时为 undefined) |
maxLength |
最大字符串长度(无约束时为 undefined) |
pattern |
需要匹配的正则表达式数组 |
值得注意的源码细节:在 api/control.ts 中,
min/max的真实类型是NonNullable<TValue> | undefined,pattern是readonly RegExp[]——即这些输入的类型跟随表单字段的泛型类型。文档表格以「数值」为例便于理解,实际实现是泛型的。
字段元数据(field metadata)
| Property | Purpose |
|---|---|
name |
字段的 name 属性(在表单与应用范围内唯一) |
此外,FormUiControl 还声明了 focus?(聚焦 UI 控件;若未实现,Signal Forms 会尝试聚焦宿主元素)与 reset?(将 UI 控件重置为初始状态)两个可选方法。下一节「添加状态信号」展示如何在控件中落地这些属性。
[formField] 指令的工作机制
[formField] 指令会检测控件实现的是哪个接口,并自动绑定对应信号。指令本身是一个 [formField] 属性选择器,源码见 directive/form_field.ts。当把 [formField] 用在自定义组件上时,接入过程可归纳为:
import {Component, signal, ChangeDetectionStrategy} from '@angular/core';
import {form, FormField, required} from '@angular/forms/signals';
import {CustomInput} from './custom-input';
import {CustomToggle} from './custom-toggle';
@Component({
selector: 'app-my-form',
imports: [FormField, CustomInput, CustomToggle],
template: `
<form novalidate>
<app-custom-input [formField]="userForm.username" />
<app-custom-toggle [formField]="userForm.subscribe" />
</form>
`,
})
export class MyForm {
formModel = signal({
username: '',
subscribe: false,
});
userForm = form(this.formModel, (schemaPath) => {
required(schemaPath.username, {message: 'Username is required'});
});
}
当绑定 [formField]="userForm.username" 时,指令依次执行:
- 检测控件实现了
FormValueControl(还是FormCheckboxControl); - 内部读取
userForm.username().value()并绑定到控件的valuemodel 信号; - 把表单状态信号(
disabled()、errors()等)绑定到控件已实现的可选输入信号上; - 之后所有更新都经由信号响应机制自动完成。
从底层源码看,检测与装配发生在 FormField.ɵngControlCreate 生命周期钩子中(form_field.ts),其优先级依次为:宿主上有 ControlValueAccessor(为兼容响应式表单)→ 宿主是自定义控件(host.customControl)→ 宿主是原生表单元素 → 否则抛出 INVALID_FIELD_DIRECTIVE_HOST 运行时错误,提示宿主必须是原生控件或带有 value/checked model 的自定义控件。这也印证了文档的结论:自定义控件与内置输入的 [formField] 用法完全一致。
负责装配自定义控件的是 customControlCreate(directive/control_custom.ts),它:
- 通过
listenToCustomControlModel监听控件的 model 变化并写入字段状态(state.controlValue.set(value)); - 通过
listenToCustomControlOutput('touch', ...)监听touch输出,回调state.markAsTouched(); - 调用
registerAsBinding把自身注册为字段的绑定; - 在每个变更检测轮次把
controlValue以及CONTROL_BINDING_NAMES列出的全部状态绑定回控件输入信号。
关于 form model 的完整创建与管理,见 Form Models 指南。
添加状态信号,让控件响应表单状态
前面最简控件「能用但不能感知状态」。通过添加可选输入信号,可以让控件响应禁用状态、渲染校验错误并记录交互痕迹。下面是一个实现了常用状态属性的完整示例:
import {Component, model, input, output, ChangeDetectionStrategy} from '@angular/core';
import {
FormValueControl,
WithOptionalFieldTree,
ValidationError,
DisabledReason,
} from '@angular/forms/signals';
@Component({
selector: 'app-stateful-input',
template: `
@if (!hidden()) {
<div class="input-container">
<input
type="text"
[value]="value()"
(input)="value.set($event.target.value)"
[disabled]="disabled()"
[readonly]="readonly()"
[class.invalid]="invalid()"
[attr.aria-invalid]="invalid()"
(blur)="touch.emit()"
/>
@if (invalid()) {
<div class="error-messages" role="alert">
@for (error of errors(); track error) {
<span class="error">{{ error.message }}</span>
}
</div>
}
@if (disabled() && disabledReasons().length > 0) {
<div class="disabled-reasons">
@for (reason of disabledReasons(); track reason) {
<span>{{ reason.message }}</span>
}
</div>
}
</div>
}
`,
})
export class StatefulInput implements FormValueControl<string> {
// Required
value = model<string>('');
// Writable interaction state - control updates these
touched = input<boolean>(false);
touch = output<void>();
// Read-only state - form system manages these
disabled = input<boolean>(false);
disabledReasons = input<readonly DisabledReason[]>([]);
readonly = input<boolean>(false);
hidden = input<boolean>(false);
invalid = input<boolean>(false);
errors = input<readonly WithOptionalFieldTree<ValidationError>[]>([]);
}
该示例展示了一条重要分工原则:
- 控件自身维护:
value(用户输入)以及touched/touch(用户交互);模板中(blur)="touch.emit()"把原生失焦事件上报给表单系统; - 表单系统维护并推送:
disabled、disabledReasons、readonly、hidden、invalid、errors——控件只负责声明input(...),指令在字段状态变化时自动填充。
WithOptionalFieldTree<ValidationError> 的含义是「错误对象可携带一个可选的 fieldTree 引用」(详见 validation_errors.ts),便于追踪错误来源字段。ValidationError 类型还包含 kind 与 message 等字段。
随后就能结合校验与状态管理使用该控件:
import {Component, signal, ChangeDetectionStrategy} from '@angular/core';
import {form, FormField, required, email} from '@angular/forms/signals';
import {StatefulInput} from './stateful-input';
@Component({
imports: [FormField, StatefulInput],
template: `
<form novalidate>
<label>
Email
<app-stateful-input [formField]="loginForm.email" />
</label>
</form>
`,
})
export class Login {
loginModel = signal({email: ''});
loginForm = form(this.loginModel, (schemaPath) => {
required(schemaPath.email, {message: 'Email is required'});
email(schemaPath.email, {message: 'Enter a valid email address'});
});
}
用户输入非法邮箱时,[formField] 指令会自动更新 invalid() 与 errors() 信号,控件随之渲染错误提示——自定义控件无需自行实现任何校验判定逻辑。
与 debounce('blur') 协同:touch 输出约定
debounce('blur') 规则会把 UI 到表单模型的更新延迟到字段失焦时才提交,而非每次按键都更新。内置控件会自动向表单报告 blur;自定义控件只有在响应原生 blur 事件时发出 touch 输出,才会参与该机制:
import {Component, model, output} from '@angular/core';
import {FormValueControl} from '@angular/forms/signals';
@Component({
selector: 'app-custom-input',
template: `
<input
type="text"
[value]="value()"
(input)="value.set($event.target.value)"
(blur)="touch.emit()"
/>
`,
})
export class CustomInput implements FormValueControl<string> {
value = model('');
touch = output<void>();
}
一旦具备 touch 输出,debounce('blur') 对自定义控件的行为将与内置输入完全一致:
import {Component, signal} from '@angular/core';
import {debounce, form, FormField} from '@angular/forms/signals';
import {CustomInput} from './custom-input';
@Component({
selector: 'app-root',
imports: [CustomInput, FormField],
template: `<app-custom-input [formField]="userForm.name" />`,
})
export class App {
userModel = signal({name: ''});
userForm = form(this.userModel, (schemaPath) => {
debounce(schemaPath.name, 'blur');
});
}
IMPORTANT:
touch必须在blur(焦点离开控件)时发出,而不是在focus时。没有touch输出,字段永远不会被标记为失焦,debounce('blur')对你的控件将完全不生效。
对应的底层连接位于 directive/control_custom.ts:指令通过 listenToCustomControlOutput('touch', () => parent.state().markAsTouched()) 把 touch 输出接到字段的 markAsTouched() 上,这也是后续 blur 类规则能够依赖 touched 状态的原因。
值转换:UI 显示值 ≠ 模型存储值
有些控件在 UI 上展示的值与表单模型存储的值并不一致——日期选择器可能显示「January 15, 2024」而模型存 2024-01-15;货币输入框可能显示 $1,234.56 而模型存 1234.56。
此时使用 transformedValue()(同样导出自 @angular/forms/signals)来让 UI 的「原始值(raw value)」与模型值保持同步。它接收控件的 value model 信号外加一对 parse / format 函数,返回一个承载原始值的可写信号:
format:把模型值转换为模板渲染所需的原始值;parse:把用户输入解析回模型值,也可以改为返回解析错误。
import {Component, model} from '@angular/core';
import {FormValueControl, transformedValue} from '@angular/forms/signals';
@Component({
selector: 'number-input',
template: `
<input type="text" [value]="rawValue()" (input)="rawValue.set($event.target.value)" />
`,
})
export class NumberInput implements FormValueControl<number | null> {
readonly value = model.required<number | null>();
protected readonly rawValue = transformedValue(this.value, {
parse: (val: string): number => ({value: val ? Number(val) : null}),
format: (val: number): string => val?.toString() ?? '',
});
}
工作机制:
- 向返回的信号写入(
rawValue.set(...))会先执行parse,把解析结果写进value(并同步更新原始值本身); - 模型值被其它途径修改时——例如
reset()、某个 schema 规则、或应用其它部分写入——format会重新执行,原始值自动跟上模型值。
源码层面,transformedValue(api/transformed_value.ts)用一个 linkedSignal 基于 format(value()) 派生原始值,并基于 createParser(value, value.set, parse) 创建内部解析器,同时把返回信号包装为带 parseErrors 属性的 TransformedValueSignal。它还向「最近的字段」注入了内部 FORM_CONTROL_INTEGRATION token 用于上报解析错误与复位(详见下两节)。
上报解析错误(parse errors)
有时原始值没有合法的模型表示——日期只输入了一半,或数字字段混入了字母。上面的 NumberInput 就有此隐患:Number('abc') 结果是 NaN,parse 却把它「愉快地」写进了模型。
改为返回 {error}:
export class NumberInput implements FormValueControl<number | null> {
readonly value = model.required<number | null>();
protected readonly rawValue = transformedValue(this.value, {
parse: (val) => {
const parsed = val ? Number(val) : null;
return Number.isNaN(parsed)
? {error: {kind: 'parse', message: `${val} is not a number`}}
: {value: parsed};
},
format: (val) => val?.toString() ?? '',
});
}
parse 的返回值(即 ParseResult<TValue>,见 transformed_value.ts)可以包含:
value:解析后的模型值;省略时模型不更新;error:解析中遇到的问题,可以是单个或多个错误对象(OneOrMany<...>);- 两者同时返回:既更新模型,又标记一个待处理问题。
当控件以 [formField] 绑定时,解析错误会被自动上报到字段,从而与校验错误一起出现在字段的 errors() 信号里:
@Component({
imports: [NumberInput, FormField],
template: `
<number-input [formField]="orderForm.amount" />
@for (error of orderForm.amount().errors(); track $index) {
<!-- {kind: 'parse', message: '...'} is reported here too -->
<p class="error">{{ error.message }}</p>
}
`,
})
export class Order {
orderModel = signal<{amount: number | null}>({amount: null});
orderForm = form(this.orderModel);
}
携带解析错误的字段视同无效,与未通过校验规则一样会阻断表单提交。这条「自动上报」链路在 transformed_value.ts 中实现:当组件处于字段上下文(注入到 FORM_CONTROL_INTEGRATION)时,解析器错误通过 integration.setParseErrors(parser.errors) 交给 FormField 合并进 errors()。
HELPFUL:原生输入也走同一套机制。当浏览器无法解析某值时(例如
<input type="date">中的半截日期),它同样会以字段上的parse错误呈现,详见 Native HTML validation。
重置控件
对字段调用 reset() 会清除挂起的解析错误,并从模型值重新执行 format,使一个停留在「不可解析状态」的控件回到干净的可展示值:
orderForm.amount().reset();
在源码层面,FormField.reset()(form_field.ts)会调用注册的 resetter,并通过 parseErrorsResetCallback 通知 transformedValue 执行内部解析器复位 parser.reset() 与重新格式化。此机制的回归测试可在 test/node/parse_errors.spec.ts 与 test/web/number_input.spec.ts 中找到。
校验集成:控件展示结果,schema 负责判定
自定义控件展示校验状态,但不执行校验。校验发生在表单 schema 中——控件通过 [formField] 指令接收 invalid() 与 errors() 信号并渲染(如前面 StatefulInput 所示)。
[formField] 指令还会透传 required、min、max、minLength、maxLength、pattern 等校验约束值,控件可以用它们增强 UI(例如把约束映射为原生 HTML 属性或提示文案):
export class NumberInput implements FormValueControl<number> {
value = model<number>(0);
// Constraint values from schema validation rules
required = input<boolean>(false);
min = input<number | undefined>(undefined);
max = input<number | undefined>(undefined);
}
当你在 schema 中加入 min() / max() 校验规则后,[formField] 指令会把这些约束值推送到控件的对应输入。该透传循环的源码实现在 control_custom.ts:对 CONTROL_BINDING_NAMES 中的每个状态(含 errors、required、min、max、minLength、maxLength、pattern 等)读取字段状态值,仅在值变化时通过 host.setInputOnDirectives(name, value) 写入控件输入信号。
IMPORTANT:不要把校验逻辑写在控件里。应在表单 schema 中定义校验规则,让控件只负责展示结果:
// Avoid: Validation in control
export class BadControl implements FormValueControl<string> {
value = model<string>('');
isValid() {
return this.value().length >= 8;
} // Don't do this!
}
// Good: Validation in schema, control displays results
accountForm = form(this.accountModel, (schemaPath) => {
minLength(schemaPath.password, 8, {message: 'Password must be at least 8 characters'});
});
一条补充的实现细节:FormField.errors(form_field.ts)在返回字段错误时会按 formField 归属过滤,确保每个控件看到的是与自身绑定相关的错误集合。
让控件可复用:配套 schema 与 apply() 组合
自定义控件往往隐含着对校验的预期。一个邮箱输入框几乎总需要 required + email 规则,若依赖每个使用方反复声明会非常繁琐。更优雅的做法是:把配套 schema 与控件打包在同一个模块里一并导出。
// email-input.ts
import {schema, required, email} from '@angular/forms/signals';
export const emailFieldSchema = schema<string>((path) => {
required(path, {message: 'Email is required'});
email(path, {message: 'Enter a valid email address'});
});
消费方导入配套 schema,并用 apply() 把它组合进自己的表单:
// registration.ts
import {form, apply} from '@angular/forms/signals';
import {emailFieldSchema} from './email-input';
registrationForm = form(this.registrationModel, (path) => {
apply(path.email, emailFieldSchema);
});
apply() 会把配套 schema 的规则合并到父表单的指定路径下,且采用组合而非替换语义——消费方仍可在同一字段上追加更多规则。schema()、apply() 以及条件组合 applyWhen() 的完整用法见 Schemas 指南。
可复用设计注意事项
- 初始值必须定义:消费方的模型必须为每个字段初始化为有定义的值。在 Signal Forms 中,
undefined表示「字段不存在」而非「空值」。因此可复用的邮箱控件要求消费方以''初始化该属性,不能让它保持undefined。初始值选型细节见 Form Models 指南。 - 不要自己注册 effect:字段状态由表单系统通过内部 effect 管理,控件通过输入信号接收状态更新。如果控件需要转换值,请使用上文
transformedValue()(见「值转换」一节),而不是自行调用effect()——自己注册 effect 会绕过表单系统的管理并造成重复的副作用执行。
从样例到工程:常见问题速查
| 需求 | 做法 | 依据 |
|---|---|---|
| 单值输入控件 | 实现 FormValueControl,提供 value = model(...) |
control.ts |
| 开关/布尔控件 | 实现 FormCheckboxControl,提供 checked = model(...) |
control.ts |
| 展示校验错误/禁用态 | 声明 errors、invalid、disabled 等可选 input |
见「可选状态属性」表格 |
支持 debounce('blur') |
提供 touch = output(),在原生 blur 时 emit |
control_custom.ts |
| UI 值 ≠ 模型值 | 用 transformedValue(value, {parse, format}) |
transformed_value.ts |
| 输入不可解析 | parse 返回 {error: {kind: 'parse', message}} |
见「Reporting parse errors」 |
| 内置校验规则跟随控件 | 同文件导出 schema(...),消费方 apply() |
见「Making controls reusable」 |
| 校验判定 | 一律放表单 schema,控件只渲染 | 见「Validation integration」 |
下一步
本文完整覆盖了构建与 Signal Forms 深度集成的自定义控件的全部关键链路:控制接口契约(含互斥的 value/checked 约束)、[formField] 指令的检测与绑定机制、可选状态信号、touch 与 debounce('blur') 的协作、transformedValue 的值转换与解析错误上报,以及用配套 schema 实现控件复用。同一主题的其余方面可继续查阅:
- Form models(表单模型):创建与管理表单模型;
- Field state management(字段状态管理):disabled、readonly、hidden 等状态的运行期管理;
- Validation(校验):在 schema 中定义校验规则与约束。
如果你正把现有响应式表单迁移到 Signal Forms,可参考 Migration 指南;Signal Forms 自带的 FormField 同时支持原生元素、实现 FormValueControl/FormCheckboxControl 的自定义控件,以及(仅为向后兼容而保留的)ControlValueAccessor,这也意味着新的自定义控件编写方式可以与旧生态共存。编写此类控件与指令的端到端行为验证,可进一步阅读仓库中的相关测试,例如 test/web/form_field.spec.ts、test/web/number_input.spec.ts 与 test/node/parse_errors.spec.ts。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0625
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00