I'm trying to use a custom property decorator called @Amount()
that would eventually format the property value into a specific format that I need.
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 is "Hello world!"
// summary.amountField is 123.00
this.summary.amountField = 456.00; <-- triggers "setting val" comment as expected
console.log(this.summary.amountField); <-- triggers "returning" comment from @Amount() as expected
console.log(this.form.controls['amountField'].value); <-- shows "Initial value"
this.form.patchValue(this.summary || {}); <-- expectation is the getter is called in order to patch it to the formcontrol, but it is not is called
console.log(this.form.controls['amountField'].value); <-- still shows "Initial value"
}
}
Is there something I'm missing, or some internal logic I'm misunderstanding about patchValue that making it not retrieve the amountField property?
To understand what's happening, you need to see the implementation of patchvalue
/setValue()
:
(Object.keys(value) as Array<keyof TControl>).forEach(name => {
assertControlPresent(this, true, name as any);
(this.controls as any)[name].setValue(
(value as any)[name], {onlySelf: true, emitEvent: options.emitEvent});
});
As you can see it iterates over the keys of the value
to update the controls.
Here is a decorator that fixes your issue :
export const Amount = () => (target: any, key: string) => {
let _value: number | string = target[key];
Object.defineProperty(target, key, {
get: () => '',
set: function (v: string) {
var val = '';
Object.defineProperty(this, key, {
get: () => {
console.log('returning', '$' + _value);
return '$' + _value;
},
set: (val) => {
console.log('setting val', val);
_value = val;
},
enumerable: true,
});
this[key] = v;
},
});
};
See this answer for the reason why.