如何让信号表单具备动态结构?
为了让我的应用中的表单管理更简单,我实现了一个表单组件:
export type MyFormModel = Record<string, number|string>;
export type MyFormFields = MyFormField[];
export type MyFormField = MyFormFieldRange | MyFormFieldText;
export interface MyFormFieldRange {
type: "range";
key: string;
min: number;
max: number;
}
export interface MyFormFieldText {
type: "text";
key: string;
required?: boolean;
}
export class MyForm {
fields = input.required<MyFormFields>();
model = model<MyFormModel>({});
form = form(this.model);
}
然而我还没有找到基于所提供的fields来生成模式的方法。
我尝试对form使用计算信号。但是,在调用表单函数时,我收到了如下错误信息:
_effect-chunk2.mjs:2601 ERROR RuntimeError: NG0203: The `_Injector` token injection failed. `inject()` function must be called from an injection context such as a constructor, a factory function, a field initializer, or a function used with `runInInjectionContext`.
也尝试在ngOnInit或ngOnChanges中延迟form的初始化,但仍然得到相同的错误。
最后尝试在模式函数中消费fields,但结果是:
ERROR RuntimeError: NG0950: Input "fields" is required but no value is available yet. Find more at https://v21.angular.dev/errors/NG0950
解决方案
据我所知,目前没有任何API能让模式像这样实现动态。
有一个相关的正在进行中的 问题,描述的是有人试图通过把字段配置放入模型来创建动态表单。正如那里所说,你可以使用 validateStandardSchema 让它动态,但这相当有限制。
然而,你可以通过在 fields 改变时重新创建表单来实现。为此,将表单和模式的创建移到 ngOnChanges。在那里,你需要创建你自己的 Injector,并且每次 fields 改变时都将其销毁,因为 form 正在注册一个需要在重新创建时清理的 effect。
export class MyForm {
injector = inject(Injector);
fields = input.required<MyFormFields>();
fieldsByKey = computed(() => new Map(this.fields().map((x) => [x.key, x])));
model = model.required<MyFormModel>();
form!: FieldTree<MyFormModel, string | number>;
formInjector: DestroyableInjector | undefined;
schemaByType = {
range: (
key: string,
schema: SchemaPathTree<MyFormModel, PathKind.Root>
) => {
min(schema[key], () => this.getField<MyFormFieldRange>(key).min);
max(schema[key], () => this.getField<MyFormFieldRange>(key).max);
},
text: (key: string, schema: SchemaPathTree<MyFormModel, PathKind.Root>) => {
required(schema[key], {
when: () => this.getField<MyFormFieldText>(key).required ?? false,
});
},
};
private getField<T>(key: string): T {
return this.fieldsByKey().get(key) as T;
}
public ngOnDestroy() {
this.formInjector?.destroy();
}
public ngOnChanges({ fields }: { fields?: SimpleChange<MyFormFields> }) {
if (fields !== undefined) {
if (this.formInjector != undefined) this.formInjector.destroy();
this.formInjector = Injector.create({
providers: [],
parent: this.injector,
});
this.form = form(
this.model,
(schema) => {
fields.currentValue.forEach((f) => {
this.schemaByType[f.type](f.key, schema);
});
},
{ injector: this.formInjector }
);
}
}
}
完整示例
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。