Angular FormGroup patchValue/setValue未触发属性装饰器getter问题
问题:自定义@Amount装饰器修饰的属性无法被Angular表单patchValue读取
我尝试使用自定义属性装饰器@Amount()将属性值格式化为特定格式,但发现Angular表单的patchValue方法无法读取该装饰器修饰的属性值,相关代码如下:
amount.decorator.ts
export const Amount = () => (target: any, key: string) => { let _value: number | string = target[key]; if (delete target[key]) { return Object.defineProperty(target, key, { configurable: false, enumerable: true, get: () => { console.log("returning", "$" + _value); return "$" + _value; }, set: (val) => { console.log("setting val", val); _value = val; }, }); } }
summary.model.ts
export class Summary { regularField!: string; @Amount() amountField!: number | string; }
form.component.ts
export class SummaryComponent implements OnInit { summary: Summary; form: FormGroup = new FormGroup({ regularField: new FormControl(''), amountField: new FormControl('Initial value'), }); ngOnInit() { this.summary = this.service.getSummary(); // summary.regularField 为 "Hello world!" // summary.amountField 为 123.00 this.summary.amountField = 456.00; <-- 如预期触发"setting val"日志 console.log(this.summary.amountField); <-- 如预期触发@Amount()的"returning"日志 console.log(this.form.controls['amountField'].value); <-- 显示"Initial value" this.form.patchValue(this.summary || {}); <-- 预期会调用getter来更新表单控件,但未触发 console.log(this.form.controls['amountField'].value); <-- 仍显示"Initial value" } }
原因分析
Angular表单的patchValue方法在处理传入对象时,只会遍历对象自身的可枚举属性(类似Object.keys()的行为)。而你的@Amount()装饰器是作用在Summary类的原型对象上的,当创建Summary实例后,amountField是原型链上的访问器属性,并非实例自身的属性,因此patchValue无法识别到这个属性,自然不会调用其getter来获取值。
解决方案
你可以通过以下几种方式解决这个问题:
方案1:手动构造包含目标属性的对象传入patchValue
直接明确指定需要赋值的属性,主动调用getter获取格式化后的值:
this.form.patchValue({ regularField: this.summary.regularField, amountField: this.summary.amountField });
方案2:在Summary类中实现toJSON方法
通过自定义toJSON方法,返回包含所有需要属性的普通对象,这样patchValue就能正确读取到值:
export class Summary { regularField!: string; @Amount() amountField!: number | string; toJSON() { return { regularField: this.regularField, amountField: this.amountField }; } }
然后调用时:
this.form.patchValue(this.summary.toJSON() || {});
方案3:修改装饰器,在实例上定义访问器属性
如果希望装饰器自动处理实例属性,可以调整装饰器逻辑,在实例创建时为其添加访问器:
export const Amount = () => (target: any, key: string) => { // 用符号属性存储实例私有值,避免命名冲突 const storageKey = Symbol(key); // 保存原构造函数 const originalConstructor = target.constructor; // 重写构造函数,在实例初始化时添加访问器 target.constructor = function(...args: any[]) { const instance = new originalConstructor(...args); // 初始化实例的私有存储值 instance[storageKey] = instance[key] ?? ''; // 在实例自身定义访问器 Object.defineProperty(instance, key, { configurable: false, enumerable: true, get: () => { console.log("returning", "$" + instance[storageKey]); return "$" + instance[storageKey]; }, set: (val) => { console.log("setting val", val); instance[storageKey] = val; }, }); return instance; }; }
内容的提问来源于stack exchange,提问作者djmistral
相关产品推荐
相关产品推荐

