I have currency directive and works good. I need add error message or red frame to currency directive (input)? Error should appears when value contains letters. I have only problem when user copy and paste value and value contains letters. For example: User copy the following value: test Now in field is displayed: test, after leave focus is displayed: test,00. I want remove letters, field should be empty in this case.
import {AfterViewInit, Directive, ElementRef, forwardRef, HostListener, Input, Renderer2} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
import {CurrencyMaskService} from "./currency-mask.service";
const noop = () => {
};
export const CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CurrencyMaskDirective),
multi: true
};
/*
* Custom Directive for Currency Mask
* The main requirements that drove the creation of this custom directive currency mask are:
* 1. Currency Mask had to be easy to implement across application. Directive control was used to accomplish that and everything is under one Module that can be easily imported.
* 2. Formatted value should be composed of: US dollar currency symbol '$' + value + 2 decimal point precision.
* 3. When user focus on the input, it should remove all formatting and only keep the decimal amount with the precision. If the input is blank and:
* a. The user types "100" then unfocus, it should display $100.00
* b. The user types "100.10" then unfocus, it should display $100.10
* c. The user types ".25" then unfocus, it should display $0.25
* 4. User shouldn't be able to type anything that isn't numbers or decimal separator "."
* 5. Optional parameter for allowing negative numbers added. On Edit mode the the indicative of negative number is the minus "-" sign, but when
* formatted we surround the value with parenthesis. So on input -300.12 will display as ($300.12).
*/
@Directive({
selector: '[appCurrencyMask]',
providers: [CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR]
})
export class CurrencyMaskDirective implements AfterViewInit, ControlValueAccessor {
private el: HTMLInputElement;
private innerValue: any;
@Input('allowNegative')
allowNegative: boolean; // Optional Parameter to allow for negative number interaction
@Input('fractionSize')
fractionSize: number;
@Input() set disabled(disabled: boolean) {
this.el.disabled = disabled ? true : null;
this.el.classList.add('disabled');
}
@Input('alignmentField') alignmentField = 'right';
constructor(private elementRef: ElementRef, private currencyMaskService: CurrencyMaskService, private renderer: Renderer2) {
this.el = elementRef.nativeElement;
}
// Placeholders for the callbacks which are later providesd
// by the Control Value Accessor
private onTouchedCallback: () => void = noop;
private onChangeCallback: (a: any) => void = noop;
// set getter
get value(): any {
return this.innerValue;
}
// set accessor including call the onchange callback
set value(v: any) {
if (v !== this.innerValue) {
this.innerValue = v;
this.onChangeCallback(v);
}
}
// From ControlValueAccessor interface
writeValue(value: any) {
if (value !== this.innerValue) {
this.el.value = this.currencyMaskService.transform(value, this.allowNegative, this.fractionSize);
if (value) {
this.renderer.setAttribute(this.elementRef.nativeElement, 'value', value);
}
this.innerValue = value;
}
}
// From ControlValueAccessor interface
registerOnChange(fn: any) {
this.onChangeCallback = fn;
}
// From ControlValueAccessor interface
registerOnTouched(fn: any) {
this.onTouchedCallback = fn;
}
ngAfterViewInit() {
// this.el.style.textAlign = 'right';
this.el.style.textAlign = this.alignmentField;
}
// On Focus remove all non-digit or decimal separator values
@HostListener('focus', ['$event.target.value'])
onfocus(value) {
// this.el.value = this.currencyMaskService.parse(value, this.allowNegative, this.fractionSize);
this.onTouchedCallback();
this.el.value = this.currencyMaskService.transform(value, this.allowNegative, this.fractionSize);
this.innerValue = this.currencyMaskService.parse(this.el.value, this.allowNegative, this.fractionSize);
this.onChangeCallback(this.innerValue);
if (this.innerValue) {
this.renderer.setAttribute(this.elementRef.nativeElement, 'value', this.innerValue);
}
}
// On Blue remove all symbols except last . and set to currency format
@HostListener('blur', ['$event.target.value'])
onBlur(value) {
this.onTouchedCallback();
this.el.value = this.currencyMaskService.transform(value, this.allowNegative, this.fractionSize);
// console.log(this.el.value);
this.innerValue = this.currencyMaskService.parse(this.el.value, this.allowNegative, this.fractionSize);
// console.log(this.innerValue);
this.onChangeCallback(this.innerValue);
if (this.innerValue) {
this.renderer.setAttribute(this.elementRef.nativeElement, 'value', this.innerValue);
}
}
// On Change remove all symbols except last . and set to currency format
@HostListener('input', ['$event.target.value'])
onChange(value) {
// this.el.value = this.currencyMaskService.transform(value, this.allowNegative, this.fractionSize);
this.innerValue = this.currencyMaskService.parse(this.el.value, this.allowNegative, this.fractionSize);
this.onChangeCallback(this.innerValue);
if (this.innerValue) {
this.renderer.setAttribute(this.elementRef.nativeElement, 'value', this.innerValue);
}
}
// Prevent user to enter anything but digits and decimal separator
@HostListener('keypress', ['$event'])
onKeyPress(event) {
//46 --> dot
//44 --> comma
const key = event.which || event.keyCode || 0;
if (key === 45 && !this.allowNegative) {
event.preventDefault();
} else if (key === 45 && this.allowNegative) {
// allow negative numbers
} else if (key !== 44 && key > 31 && (key < 48 || key > 57)) {
event.preventDefault();
}
}
}
import {Injectable} from '@angular/core';
const padding = "000000000";
@Injectable()
export class CurrencyMaskService {
private prefix: string;
private thousandsSeparator: string = ".";
private decimalSeparator: string = ',';
constructor() {
this.prefix = '';
this.thousandsSeparator = '.';
this.decimalSeparator = ','
}
transform(value: string, allowNegative = false, fractionSize: number) {
if(value && !this.isValueParsedForTheFirstTime(value)){
value = value.replace(/[.]/g, "");
}
if (value == undefined || value === '') {
return null;
}
if (allowNegative) {
value = value.toString();
if (value.startsWith('(') || value.startsWith('-')) {
value = '-' + value.substr(1, value.length).replace(/\(|\)|\$|\-/g, '');
} else {
value = value.replace(/\(|\)|\$|\-/g, '');
}
} else {
value = value.toString();
value = value.replace(/\(|\)|\$|\-/g, '');
}
value = value.toString().replace('.', ',');
let [integer, fraction = ''] = (value || '').toString().split(this.decimalSeparator);
fraction = fractionSize > 0
? this.decimalSeparator + (fraction + padding).substring(0, fractionSize) : "";
// If user types .xx we can display 0.xx
if (integer === '') {
integer = '0.00';
} else if (integer.startsWith('$')) {
// If there are multiple transforms, remove the previous dollar sign (blur and change at the same time)
integer = integer.substr(1, integer.length);
} else if (allowNegative && integer.startsWith('-')) {
// If user inputs negative number set to paranthesis format
integer = integer.substr(1, integer.length);
// return '(' + this.prefix + integer + fraction + ')';
return '-' + this.prefix + integer + fraction;
}
integer = integer.replace(/\B(?=(\d{3})+(?!\d))/g, this.thousandsSeparator);
let result = integer + fraction;
return this.prefix + integer + fraction;
}
isValueParsedForTheFirstTime(value){
value = value.toString();
return !(value.indexOf('.') !== -1 && value.indexOf(",") !== -1);
}
// transform(value: string, allowNegative = false, fractionSize: number) {
// if (value == undefined || value === '') {
// return null;
// }
// if (allowNegative) {
// value = value.toString();
// if (value.startsWith('(') || value.startsWith('-')) {
// value = '-' + value.substr(1, value.length).replace(/\(|\)|\$|\-/g, '');
// } else {
// value = value.replace(/\(|\)|\$|\-/g, '');
// }
// }
// value = value.toString().replace('.', ',');
// let [integer, fraction = ''] = (value || '').toString().split(this.decimalSeparator);
// fraction = fractionSize > 0
// ? this.decimalSeparator + (fraction + padding).substring(0, fractionSize) : "";
// // If user types .xx we can display 0.xx
// if (integer === '') {
// integer = '0.00';
// } else if (integer.startsWith('$')) {
// // If there are multiple transforms, remove the previous dollar sign (blur and change at the same time)
// integer = integer.substr(1, integer.length);
// } else if (allowNegative && integer.startsWith('-')) {
// // If user inputs negative number set to paranthesis format
// integer = integer.substr(1, integer.length);
// return '(' + this.prefix + integer + fraction + ')';
// }
// return this.prefix + integer + fraction;
// }
parse(value: string, allowNegative = false, fractionSize: number): number {
let [integer, fraction = ''] = (value || '').split(this.decimalSeparator);
integer = integer.replace(this.thousandsSeparator, "");
fraction = parseInt(fraction, 10) > 0 && 2 > 0 ? this.decimalSeparator + (fraction + padding).substring(0, 2) : '';
// let nValue = (integer + fraction).replace(",", ".").toString();
integer = integer.replace(/[.]/g, "");
let nValue = (integer + "." + fraction.replace(",", "")).toString();
if (nValue && nValue === '.') {
return null;
}
if (allowNegative && value.startsWith('-')) {
// if (allowNegative && value.startsWith('(') && value.endsWith(')')) {
// return parseFloat(nValue);
// console.log(nValue + ' nv');
// return (-1 * parseFloat(nValue));
let parsed = parseFloat(nValue);
if (isNaN(parsed)) {
return null;
}
return parsed;
// return parseFloat(nValue);
} else {
let parsed = Number(nValue);
if (isNaN(parsed)) {
return null;
}
return parsed;
// return Number(nValue);
}
// if (allowNegative && value.startsWith('(') && value.endsWith(')')) {
// return (-1 * parseFloat(nValue));
// } else {
// return Number(nValue);
// }
}
}
<input matInput type="text"
appCurrencyMask
[fractionSize]="2"
[allowNegative]="false"
[alignmentField]="'left'"
#fTotalField="ngModel"
[(ngModel)]="projectData.total"
trim="blur">