I am trying to format a number to currency while the user typing in the field, which actually changes the control actual value to formatted value(number to a string)
.
Is there any way to format the number to currency for view purposes only, without changing the control's actual value to string? and keep a "normal" number in the model Because I have another directive that has a min and max value which depends on this control's actual value in number format.
Input Element:
<input
digitOnly
type="text"
currencyFormatter
[decimal]="false"
id="salary-range"
class="form-control"
formControlName="maxSalary"
placeholder="Enter Max Salary"
[min]="formcontrol['minSalary'].value || 1"
[max]="formcontrol['currency'].value === 'INR'? 100000000: 1000000"
/>
Directive:
@Directive({
selector: '[currencyFormatter]'
})
export class CurrencyFormatterDirective implements AfterViewInit, OnDestroy {
constructor(public el: ElementRef, @Self() private ngControl: NgControl) {
this.inputElement = el.nativeElement;
this.formatter = new Intl.NumberFormat('en-IN', {
style: 'currency',
currency: 'INR',
minimumFractionDigits: 0,
maximumFractionDigits: 0
});
}
private formatter: Intl.NumberFormat;
private inputElement: HTMLInputElement;
private destroy$ = new Subject<boolean>();
ngAfterViewInit(): void {
this.ngControl.control?.valueChanges
.pipe(takeUntil(this.destroy$))
.subscribe(this.updateValue.bind(this));
}
updateValue(value: string) {
let inputVal = value || '';
const unformatted = this.unformatPrice(inputVal);
const formatted = this.formatPrice(unformatted);
this.setValue(formatted);
}
private formatPrice(value: number): string {
return this.formatter.format(value);
}
private unformatPrice(value: string): number {
return new RegExp(/[$₹., ]/g).test(value)
? Number(value.substring(1).replace(/,/g, ''))
: Number(value);
}
private setValue(value: string | number): void {
this.ngControl.control?.setValue(value, { emitEvent: false });
}
ngOnDestroy(): void {
this.destroy$.next(true);
this.destroy$.complete();
}
}
I have achieved this using ControlValueAccessor
. I have created a component and added the input where I am showing the formatted currency value and with the help of ControlValueAccessor I have sent the actual value.
Currency Component HTML:
<input
digitOnly
type="text"
[id]="inputId"
[min]="minValue"
[max]="maxValue"
[decimal]="false"
[isCurrency]="true"
class="form-control"
[allowNegatives]="false"
[formControl]="currency"
[autocompleteOff]="'off'"
[placeholder]="placeholder"
/>
Currency Component TS:
@Component({
selector: 'currency-input',
templateUrl: './currency-input.component.html',
styleUrls: ['./currency-input.component.css'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CurrencyInputComponent),
multi: true
}
]
})
export class CurrencyInputComponent
implements OnInit, OnDestroy, ControlValueAccessor
{
constructor() {}
public isDisabled = false;
@Input('min') minValue!: number;
@Input('max') maxValue!: number;
public currencyValue: string = '';
@Input('input-id') inputId!: string;
private onTouched: Function = () => {};
@Input('placeholder') placeholder!: string;
public onValidationChange: Function = () => {};
private propagateChange: Function = (_: number) => {};
private unsubscribe$: Subject<void> = new Subject<void>();
@Input('currency-format') currencyFormat: 'INR' | 'USD' = 'INR';
public currency = new FormControl('', {
nonNullable: true
});
ngOnInit(): void {
this.currency.valueChanges
.pipe(
takeUntil(this.unsubscribe$),
map(this.currencyToNumber),
tap((value) => {
this.setValue(value);
this.propagateChange(value);
})
)
.subscribe();
}
private numberToCurrency(value: number): string {
return new Intl.NumberFormat(
`en-${this.currencyFormat === 'INR' ? 'IN' : 'US'}`,
{
style: 'currency',
currency: this.currencyFormat,
minimumFractionDigits: 0,
maximumFractionDigits: 0
}
).format(value);
}
private currencyToNumber(value: string): number {
return new RegExp(/[$₹., ]/g).test(value)
? Number(value.substring(1).replace(/,/g, ''))
: Number(value);
}
private setValue(value: number | null): void {
const formattedValue = !!value ? this.numberToCurrency(value) : '';
this.currency.patchValue(formattedValue, {
emitEvent: false
});
}
public writeValue(value: number | null): void {
this.setValue(value);
}
public registerOnChange(fn: Function): void {
this.propagateChange = fn;
}
registerOnValidatorChange?(fn: () => void): void {
this.onValidationChange = fn;
}
public registerOnTouched(fn: Function): void {
this.onTouched = fn;
}
public setDisabledState?(isDisabled: boolean): void {
isDisabled ? this.currency.disable() : this.currency.enable();
}
ngOnDestroy(): void {
this.unsubscribe$.next();
this.unsubscribe$.complete();
}
}