How to format number input in angular
Asked Answered
P

3

11

I have a number input like this enter image description here

and i want something like this (no matters what the sepparator will be)

enter image description here

My actual code works as follows:

<div class="row center-xs" *ngFor="let item of listItem;let i=index">
    <div class="col-xs-12 col-md-10">
        <mat-form-field>
            <input [disabled]="item.lock!=0" type="number" matInput [placeholder]="item.name" [(ngModel)]="item.concept" >                                      
        </mat-form-field>
    </div>
</div>

How can i achieve this?

Pyromancy answered 12/10, 2018 at 22:7 Comment(0)
B
3

The directive code has some bugs that I fixed.

I also fixed the problem with converting from string to number. The variable linked to the input was assigned a string value instead of a number and this caused errors when sending data to the APIs.

The ngModule would no longer be used, it could be used but it would be superfluous.

its use would remain in this way.

<input type="text" matInput [(localizedNumericInput)]="invoice.value">

import { formatNumber } from '@angular/common';
import { Directive, ElementRef, EventEmitter, forwardRef, HostListener, Input, Output } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { MAT_INPUT_VALUE_ACCESSOR } from '@angular/material/input';


@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 {
  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);
  }

  @Input('localizedNumericInput') set valor(valor:number) {
    if (!valor) {valor = 0;}

    this.formatValue(valor.toString());
  }
  @Output('localizedNumericInputChange') valorChange = new EventEmitter<number>();


  @Input('objeto') objeto: any;
  @Input('propiedad') propiedad: string;

  @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
    let [integer, decimal] = value.replace(regExp, '').split(this.decimalMarker);

    if (!integer) { integer = 0; }
    if (!decimal) { decimal = 0; }

    //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);

    this.valorChange.emit(parseFloat(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.toString())) {
      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
    let [integer, decimal] = value.toString().split('.');

    if (!integer) { integer = '0'; }
    if (!decimal) { decimal = '00'; }

    //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');
    let [integer, decimal] = value.replace(regExp, '').split(this.decimalMarker);

    if (!integer) { integer = '0'; }
    if (!decimal) { decimal = '00'; }

    this._value = integer.concat('.', decimal);
    if (value) {
      this.element.nativeElement.value = this._value;
    } else {
      this.element.nativeElement.value = '';
    }
  }
}
Bankston answered 3/6, 2022 at 6:25 Comment(2)
Thank you for share this. May be helpful for someonePyromancy
formatNumber throws errors when using locales that aren't registered with Angular. Here's a browser version of the same line. const [thousandSeparator, decimalMarker] = new Intl.NumberFormat(this.locale, { useGrouping: true }).format(1000.99).replace(/\d/g, '');Burrus
N
9

I had a similar problem. 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 = '';
    }
  }
}
Nagle answered 5/2, 2020 at 9:45 Comment(2)
Well, i had forgotten about this question for some time and i finally solved it by separating the formatting functions and the angular's binding itself. But this approach by directives is by far so much more accurate. I will mark this as accepted. Thanks for provide this solution, i think it will be useful for someonePyromancy
@alexander-poshtaruk is the real hero here :)Nagle
B
3

The directive code has some bugs that I fixed.

I also fixed the problem with converting from string to number. The variable linked to the input was assigned a string value instead of a number and this caused errors when sending data to the APIs.

The ngModule would no longer be used, it could be used but it would be superfluous.

its use would remain in this way.

<input type="text" matInput [(localizedNumericInput)]="invoice.value">

import { formatNumber } from '@angular/common';
import { Directive, ElementRef, EventEmitter, forwardRef, HostListener, Input, Output } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { MAT_INPUT_VALUE_ACCESSOR } from '@angular/material/input';


@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 {
  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);
  }

  @Input('localizedNumericInput') set valor(valor:number) {
    if (!valor) {valor = 0;}

    this.formatValue(valor.toString());
  }
  @Output('localizedNumericInputChange') valorChange = new EventEmitter<number>();


  @Input('objeto') objeto: any;
  @Input('propiedad') propiedad: string;

  @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
    let [integer, decimal] = value.replace(regExp, '').split(this.decimalMarker);

    if (!integer) { integer = 0; }
    if (!decimal) { decimal = 0; }

    //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);

    this.valorChange.emit(parseFloat(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.toString())) {
      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
    let [integer, decimal] = value.toString().split('.');

    if (!integer) { integer = '0'; }
    if (!decimal) { decimal = '00'; }

    //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');
    let [integer, decimal] = value.replace(regExp, '').split(this.decimalMarker);

    if (!integer) { integer = '0'; }
    if (!decimal) { decimal = '00'; }

    this._value = integer.concat('.', decimal);
    if (value) {
      this.element.nativeElement.value = this._value;
    } else {
      this.element.nativeElement.value = '';
    }
  }
}
Bankston answered 3/6, 2022 at 6:25 Comment(2)
Thank you for share this. May be helpful for someonePyromancy
formatNumber throws errors when using locales that aren't registered with Angular. Here's a browser version of the same line. const [thousandSeparator, decimalMarker] = new Intl.NumberFormat(this.locale, { useGrouping: true }).format(1000.99).replace(/\d/g, '');Burrus
P
-3

You can make use of the 'number' pipe of Angular.

https://angular.io/api/common/DecimalPipe

This pipe controls the fraction digits as well as how a number will be formatted based on a locale (default: en-US).

So a number like: 2000000 will be transformed to 2,000,000. Depending on the locale the separators will be differ.

@Component({
  selector: 'number-pipe',
  template: `
    <!--output '2,000,000'-->
    {{e | number}}
  `
})
export class NumberPipeComponent {
  e: number = 2000000;
}
Pepsinate answered 12/10, 2018 at 22:46 Comment(2)
In this case i can't use the pipe, because i'm not using numbers on the way you talk and i'm using two way binding on the input, not only posting the valuePyromancy
You can't use pipes in input controls.Schechinger

© 2022 - 2024 — McMap. All rights reserved.