How to get the amount converted to currency pipe value after getting error message in angular8

Viewed 5900

i am using currency pipe in blur event. But it works fine for the first time, when i get an error message of validation, if i delete few numbers and come out, it wont be formatted to the currency format remains in the format what user have deleted. For example i gave this number: 36543265 so when i came out of the input it is formatted to $36,543,265.00, with the validation error message. So if i delete 265.00 from $36,543,265.00 , i still have the error validation message. so the validation error message went off and stayed with this format $36,543 but it didn't come to the proper format. How do i make it to proper currency pipe format of USD, after clearing the validation message.

How can i make it to be formatted properly based on user deletion of values.

TS:

public transformAmount(element, name) {
  if (name == "amount") {
    let formattedAmount = this.currencyPipe.transform(this.eoInfoForm.value.amount, 'USD');
    element.target.value = formattedAmount;
    this.eoInfoForm.get('amount').setValue(formattedAmount);
    if (this.eoInfoForm.get('amount').status === 'VALID') {
      this.amt = false; 
    } 
    else {
      this.amt = true;
    } 
  } 
}

HTML:

 <input type="text" class="form-control" placeholder="Amount in dolars"
                    formControlName="amount" autocomplete="off" maxlength="8" allowNumberOnly (blur)="transformAmount($event,'amount')" [ngClass]="{ 'is-invalid': amt  && eoInfo.amount.invalid }">
                    <div *ngIf="amt && eoInfo.amount.invalid" class="invalid-feedback">
                        <div *ngIf="amt && eoInfo.amount.invalid">Maximum of 8 characters including $ is allowed</div>
                      </div>

DEMO: DEMO

4 Answers

you can write a somewhat simple directive to do this:

import { Directive, HostListener, ElementRef, OnInit, Input } from "@angular/core";
import { CurrencyPipe } from '@angular/common';

@Directive({ selector: "[currencyInput]" })
export class CurrencyInputDirective implements OnInit {

  // build the regex based on max pre decimal digits allowed
  private regexString(max?: number) {
    const maxStr = max ? `{0,${max}}` : `+`;
    return `^(\\d${maxStr}(\\.\\d{0,2})?|\\.\\d{0,2})$`
  }
  private digitRegex: RegExp;
  private setRegex(maxDigits?: number) {
    this.digitRegex = new RegExp(this.regexString(maxDigits), 'g')
  }
  @Input()
  set maxDigits(maxDigits: number) {
    this.setRegex(maxDigits);
  } 

  private el: HTMLInputElement;

  constructor(
    private elementRef: ElementRef,
    private currencyPipe: CurrencyPipe
  ) {
    this.el = this.elementRef.nativeElement;
    this.setRegex();
  }

  ngOnInit() {
    this.el.value = this.currencyPipe.transform(this.el.value, 'USD');
  }

  @HostListener("focus", ["$event.target.value"])
  onFocus(value) {
    // on focus remove currency formatting
    this.el.value = value.replace(/[^0-9.]+/g, '')
  }

  @HostListener("blur", ["$event.target.value"])
  onBlur(value) {
    // on blur, add currency formatting
    this.el.value = this.currencyPipe.transform(value, 'USD');
  }

  // variable to store last valid input
  private lastValid = '';
  @HostListener('input', ['$event'])
  onInput(event) {
    // on input, run regex to only allow certain characters and format
    const cleanValue = (event.target.value.match(this.digitRegex) || []).join('')
    if (cleanValue || !event.target.value)
      this.lastValid = cleanValue
    this.el.value = cleanValue || this.lastValid
  }
}

the directive converts the value to a number on focus and back to a formatted currency string on blur.

use like:

<input type="text" class="form-control" placeholder="Amount in dolars" autocomplete="off"
  formControlName="amount" currencyInput maxDigits="9" 
  [ngClass]="{ 'is-invalid': eoInfo.amount.dirty  && eoInfo.amount.invalid }">

blitz: https://stackblitz.com/edit/angular-csyslb?file=src%2Fapp%2Fcurrency-input.directive.ts

You have an error with your code after you insert the $ sign enter image description here

You can use this regex to take only numbers from the text

var thenum = thestring.replace( /^\D+/g, ''); // replace all leading non-digits with nothing

or just check that the first char and if it's $ remove it

I've changed some things in your code in order to make it work: Working solution

Here is the main part:

public transformAmount(name) {
    let formattedAmount = this.currencyPipe.transform(
        this.eoInfoForm.value.amount.replace(/[^\d.E+]/gm, ""),
        "USD"
    );
    this.eoInfoForm.get(name).setValue(formattedAmount);
}

What I did is invert the pipe transformation on the value before passing it again to the pipe. I used the /[^\d.E+]/gm regular expression to remove everything that is not included in :

  • Number
  • Dot character
  • "E" character
  • "+" character

The "E" and "+" are to handle a specific case when the number is very large and is displayed like $1.00E+37.

I supposed that the max length of 8 is applicable when the text has been formatted, so I didn't touch it (otherwise you need a custom validator). I just moved it to the TypeScript file since you are using reactive forms so it's better not mixing template forms concepts.

First thing I want to point out:

You use maxlength="8" which is fine until you start using the currency pipe. The characters added by your currency pipe will be taken into account. I don't know if that's a wanted behavior, but it definitely isn't intuitive from a user perspective.

"I see a limit of 8 characters, I can input up to 8 characters" - is the first thing I'd think when reading the validation message. I don't want to have to think of the currency formatting myself while I input my (at the time) non-formatted number.

I'd say either make the maxLength 14 (8 + 1 dollar sign + 2 commas + 1 dot + double digits is the max number possible for USD = 14 chars) and leave the validation message as value as 8. Or if you really only want 8 chars after formatting, make the validation message value as 3 (3 + 1 dollar sign + 1 commas + 1 dot + double digits = 7) having 4 digits goes to 9 chars after formatting because the extra digit + a comma are added, going to 9 chars which is more than the maxLength 8.

You can also use a custom validation for the length and properly check yourself (which is good if currency format can vary) instead of relying on the generic *ngIf="amt && eoInfo.amount.invalid"


Now for your actual issue, a solution I think would be nice is to remove the formatting while editing. It's never really practical for a user to edit and have to navigate through . , and $ when all they care when editing are the numbers really. You can make a "reverse currency pipe" when the user focuses the field so all they see are the number.

I made a quick fork of your Stackblitz demo with the assumption that the max length refers to the number of digits before the decimal point.

Let me know if anything needs changing and if that helps solve your issue at all.

Related