I have an input field and want to enter a float number using a German locale. My solution is following, it is working, but it is complicated and there should be a better approach?
<input matInput [ngModel]="amount | number: '0.2-2'"
(blur)="transformAmount($event)" (keyup.enter)="transformAmount($event)"/>
transformAmount(event) {
console.log(event.target.value);
this.amount = parseFloat(event.target.value.replace('.', '').replace(',','.'));
console.log('amount=' + this.amount);
}
I had a similar problem with the formatting reactive form input field. To solve it I have used this article. I have created a slightly modified version to fit my needs.
import { Directive, ElementRef, forwardRef, HostListener, Input, OnDestroy } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { MAT_INPUT_VALUE_ACCESSOR } from '@angular/material';
import { Subscription } from 'rxjs';
import { formatNumber } from '@angular/common';
@Directive({
selector: 'input[localizedNumericInput]',
providers: [
{ provide: MAT_INPUT_VALUE_ACCESSOR, useExisting: LocalizedNumericInputDirective },
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => LocalizedNumericInputDirective),
multi: true
}
]
})
export class LocalizedNumericInputDirective implements ControlValueAccessor, OnDestroy {
locale = 'en';
decimalMarker: string;
constructor(private element: ElementRef<HTMLInputElement>) {
}
private _value: string | null;
get value(): string | null {
return this._value;
}
@Input('value')
set value(value: string | null) {
this._value = value;
this.formatValue(value);
}
@HostListener('input', ['$event.target.value'])
input(value) {
//Find all numerics, decimal marker(, or .) and -
//It will delete thousandSeparator cos it's always opposite to decimal marker
const regExp = new RegExp(`[^\\d${this.decimalMarker}-]`, 'g');
//Separate value on before and after decimal marker
const [integer, decimal] = value.replace(regExp, '').split(this.decimalMarker);
//Send non localized value, with dot as decimalMarker to API
this._value = decimal ? integer.concat('.', decimal) : integer;
// If decimal separator is last character don't update
// because it will delete . || ,
if (this.isLastCharacterDecimalSeparator(value)) {
this._value = value;
}
// here to notify Angular Validators
this._onChange(this._value);
}
@HostListener('blur')
_onBlur() {
/**
* Adding thousand separators
*/
this.formatValue(this._value);
}
@HostListener('focus')
onFocus() {
this.unFormatValue();
}
_onChange(value: any): void {}
/**
* @param value
* apply formatting on value assignment
*/
writeValue(value: any) {
this._value = value;
this.formatValue(this._value);
}
registerOnChange(fn: (value: any) => void) {
this._onChange = fn;
}
registerOnTouched() {}
isLastCharacterDecimalSeparator(value: any) {
return isNaN(value[value.length - 1]);
}
private formatValue(value: string | null) {
if (value === null) {
this.element.nativeElement.value = '';
return;
}
if (this.isLastCharacterDecimalSeparator(value)) {
this.element.nativeElement.value = value;
return;
}
// Conclude the decimal and thousand separators from locale
const [thousandSeparator, decimalMarker] = formatNumber(1000.99, this.locale).replace(/\d/g, '');
this.decimalMarker = decimalMarker;
//Here value should always come with . as decimal marker thus any other behavior is bug
const [integer, decimal] = value.split('.');
//Group every three elements, and add thousandSeparator after them
this.element.nativeElement.value = integer.replace(/\B(?=(\d{3})+(?!\d))/g, thousandSeparator);
//Add decimals and decimalMarker if any
if (decimal) {
this.element.nativeElement.value = this.element.nativeElement.value.concat(decimalMarker, decimal);
}
}
private unFormatValue() {
const value = this.element.nativeElement.value;
if (this.isLastCharacterDecimalSeparator(value)) {
return;
}
const regExp = new RegExp(`[^\\d${this.decimalMarker}-]`, 'g');
const [integer, decimal] = value.replace(regExp, '').split(this.decimalMarker);
this._value = integer.concat('.', decimal);
if (value) {
this.element.nativeElement.value = this._value;
} else {
this.element.nativeElement.value = '';
}
}
}
To use this directive your html should look like this:
<mat-form-field>
<mat-label>Value</mat-label>
<input
type="text"
localizedNumericInput
matInput
autocomplete="off"
formControlName="value"
/>
</mat-form-field>
Your approach is fine.
There is no better way to transform data from variable to input
than Pipe
and there is no better way to transform data from input
to variable than a function bound to DOM events.
In order to simplify your code a bit you could do the following:
<input matInput [ngModel]="amount | number: '0.2-2'" (ngModelChange)="transformAmount($event)"/>
transformAmount(event) {
console.log(event);
this.amount = parseFloat(event.replace('.', '').replace(',','.'));
console.log('amount=' + this.amount);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With