Using reactive forms in Angular I created two directives one that converts values from cents to dollars by dividing by 100, and one that converts dollars to cents by multiplying by 100. This works when I manually set the form controls, but it doesn't get applied OnInit, and it doesn't get applied when a form input is shown when hidden behind an *ngIf.
I created a stackblitz with an example. It should OnInit show $12,345.50
but it instead shows $12,34550.00
and the same value when a form field is shown until they are manually set using the buttons. Example is in Angular v15, but I'm using v18 if that helps with a solution, but able to replicate it similarly in either.
Is there a way to have the directives run OnInit and when the input is shown? Or should I be implementing this differently?
We can access the NgControl
directive and directly patch the transformed value on first load.
Also, do not use *ngIf
when working with hiding forms. It makes the controls not work properly, since it's destroyed from DOM; always go for [hidden]
attribute instead:
import { AfterContentInit, Directive, Input, inject } from '@angular/core';
import {
DefaultValueAccessor,
FormControlName,
NgControl,
} from '@angular/forms';
import {
maskitoNumberOptionsGenerator,
maskitoParseNumber,
} from '@maskito/kit';
export const dollarMask = maskitoNumberOptionsGenerator({
min: 0,
// Maximum to avoid floating point precision errors
// which will occur when numeric values are too large
// exceeding Number.MAX_SAFE_INTEGER
max: 1000000000,
precision: 2,
decimalSeparator: '.',
decimalZeroPadding: true,
thousandSeparator: ',',
prefix: '$',
});
@Directive({
standalone: true,
selector: '[maskito][toDollars]',
})
export class ToDollarsDirective {
private readonly accessor = inject(DefaultValueAccessor, { self: true });
private readonly formControlName = inject(NgControl, { self: true });
ngOnInit() {
const initialValue = this.formControlName.value;
const dollars1 = initialValue != null ? initialValue / 100 : initialValue;
this.formControlName.control!.patchValue(dollars1);
}
ngOnAfterContentInit() {
const original = this.accessor.writeValue.bind(this.accessor);
this.accessor.writeValue = (value: any) => {
const dollars = value != null ? value / 100 : value;
original(dollars);
};
}
}
@Directive({
standalone: true,
selector: '[maskito][toCents]',
})
export class ToCentsDirective {
private readonly accessor = inject(DefaultValueAccessor, { self: true });
private readonly formControlName = inject(NgControl, { self: true });
ngAfterContentInit() {
const original = this.accessor.onChange.bind(this.accessor);
this.accessor.onChange = (value: any) => {
const cents = maskitoParseNumber(value) * 100;
original(cents);
};
}
}