Angular 2: Cursor issue with “number” pipe on ngMo

2019-04-09 01:29发布

问题:

I have an input that I want to be displayed like currency. I want only two decimal places to be allowed and for it to only allow numbers while automatically adding commas when necessary. Basically, if the user types "12345", I want the input to automatically display as "12,345.00". "12,345" would be acceptable too, but if they type in "12345.5" then it would need to be displayed as "12,345.50". I'm trying to use pipes to accomplish this and decided to use the "number" pipe since I don't want a currency symbol shown (I already have a dollar sign as a label before the input).

Here is my code:

<input [ngModel]="Amount | number: '1.2-2'" (ngModelChange)="updateAmount($event)" class="form-control" id="Amount" name="Amount" tabindex="4" type="number" autocomplete="off">

I'm having a few problems.

  1. When I type a number, it automatically adds a decimal and two 0's at the end, which is fine, but it also adds the cursor to the very end of that, so if I type "55", instead of displaying as "55.00", it displays as "5.01" (I assume it interprets it as 5.005 and then rounds it to 5.01). How can I prevent the cursor from going to the very end so users can type naturally while seeing the result they expect?
  2. This filter isn't actually limiting input to 2 decimal places. If I type "1234" it will display as "1.00234". It will also allow me to add multiple decimal points. How can I limit it to one decimal point with only 2 digits after it?
  3. It is really easy to break this pipe with incorrect input. For example, if the user types in a letter, I will get an error in the console that says something like:

Invalid argument '11.00a' for pipe 'DecimalPipe'

After this error, the filter completely stops working.

If I set the input to type="number" and I type in 1234, the value will be 1,234, but the input will disappear and I will get the following message in my console:

The specified value "1,234" is not a valid number. The value must match to the following regular expression: -?(\d+|\d+.\d+|.\d+)([eE][-+]?\d+)?

Using JQuery Inputmask gives me the results I want in terms of restricting/displaying the input, but it breaks my ngModel and sets the value to empty, so that isn't an option for me unless someone knows a way around that.

Are there changes I can make to my pipe to get me the results I want? How can I get this to work?

回答1:

Here is the aforementioned inspired directive for masking the input: https://plnkr.co/edit/aBvO2F?p=preview

import { Directive } from "@angular/core";
import { NgControl } from "@angular/forms";

@Directive({
  selector: '[ngModel][decimal]',
  host: {
    '(ngModelChange)': 'onInputChange($event)'
  }
})
export class DecimalMask {
  constructor(public model: NgControl) {}

  onInputChange(event, backspace) {
    var valArray = event.toString().split('.') : [];
    for(var i = 0; i < valArray.length; ++i) {
      valArray[i] = valArray[i].replace(/\D/g, '');
    }

    var newVal: number;

    if(valArray.length === 0) {
      newVal = '';
    }
    else {
      let matches = valArray[0].match(/[0-9]{3}/mig);

      if(matches !== null && valArray[0].length > 3) {
        let commaGroups = Array.from(Array.from(valArray[0]).reverse().join('').match(/[0-9]{3}/mig).join()).reverse().join('');
        let replacement = valArray[0].replace(commaGroups.replace(/\D/g, ''), '');

        newVal = (replacement.length > 0 ? replacement + "," : "") + commaGroups;
      } else {
        newVal = valArray[0];
      }

      if(valArray.length > 1) {
        newVal += "." + valArray[1].substring(0,2);
      }
    }
    // set the new value
    this.model.valueAccessor.writeValue(newVal);
  }
}

Input element looks like:

<input decimal [(ngModel)]="Amount"
    class="form-control" id="Amount" name="Amount" tabindex="4" autocomplete="off">

Check guard if last character is alpha or length past decimals > 2:

ngDoCheck() {
    console.log(this.Amount);
    if(this.Amount) {
      this.Amount = this.Amount.replace(/[A-Za-z]/g, '');
      if(this.Amount.indexOf('.') !== -1) {
        var arrayVals = this.Amount.split('.');
        this.Amount = arrayVals[0] + "." + arrayVals[1].slice(0,2);
      }
    }
  }