6

I need to add a space after every 4th digit I enter, I am getting this in the console, how can I can achieve this to change in the input in Angular 5.

Code I used given below .ts

  mychange(val) {
    const self = this;
    let chIbn = val.split(' ').join('');
    if (chIbn.length > 0) {
      chIbn = chIbn.match(new RegExp('.{1,4}', 'g')).join(' ');
    }
    console.log(chIbn);
    this.id = chIbn;
  }

HTML

<input class="customerno" (ngModelChange)="mychange($event)" [formControl]="inputFormControl" (keydown.backspace)="onKeydown($event)" maxlength="{{digit}}" (keyup)="RestrictNumber($event)" type="tel" matInput [(ngModel)]="id" placeholder="Customer No. ">

Console:

1
11
111
1111
1111 1
1111 11
1111 111
1111 1111
1111 1111 1
1111 1111 11
1111 1111 111
1111 1111 1111

enter image description here

I adapted it from Angular 2 : add hyphen after every 4 digit in input , card number input. but I changed the hypen to a space.

Tony Brasunas
  • 4,012
  • 3
  • 39
  • 51

2 Answers2

10

I would recommend to check out this Directive

import { Directive, HostListener, ElementRef } from '@angular/core';

@Directive({
  selector: '[credit-card]'
})
export class CreditCardDirective {

@HostListener('input', ['$event'])
  onKeyDown(event: KeyboardEvent) {
    const input = event.target as HTMLInputElement;

    let trimmed = input.value.replace(/\s+/g, '');
    if (trimmed.length > 16) {
      trimmed = trimmed.substr(0, 16);
    }

    let numbers = [];
    for (let i = 0; i < trimmed.length; i += 4) {
      numbers.push(trimmed.substr(i, 4));
    }

    input.value = numbers.join(' ');

  }
}

and use in your html template as

<input type="text" credit-card>

Here is the source code

UPDATE: (10/10/2019) Input type should be only text (default type)

Coffee-Tea
  • 1,139
  • 9
  • 10
  • 1
    Note: this doesn't work if your input is `type="number"`. If you use it on a `number` input, you will get a `The specified value "0000 0" is not a valid number. The value must match to the following regular expression: -?(\d+|\d+\.\d+|\.\d+)([eE][-+]?\d+)?` error from your directive, when you get past the first set of 4 digits, and the input will reset to empty. – Geoff James Oct 09 '19 at 17:48
  • @Coffee-Tea -- no problem. Just as an aside to your edit: I found this directive *does* work with other input types, not **only** `text`. It won't work with `number`, as a number input with spaces is invalid, full stop. As a workaround for my scenario, where I needed leading 0s at the start of the number, using spaces, and I wanted to display a numeric keyboard on mobile devices - I used `tel` input type (with a number/space -only regex pattern); and it works fine for me. I do also believe it will work with other input types with flexibility, for example `search` and also for `textarea`s. – Geoff James Oct 10 '19 at 09:14
  • 1
    I went with `tel` in my solution below, and that seems to be working well. – Tony Brasunas May 16 '20 at 01:29
3

Don't forget to handle Backspace, Cursor Position, and American Express

I had to handle some extra complexity, but it's likely what many people will confront when developing this. We need to consider use of the Backspace key and arrow keys when rewriting the input value. There is also American Express numbers to consider, which are not simply 4-4-4-4 numbers.

Here's how I did it in a component using a template reference variable and cursor position detection. (No need for a custom directive if you only have one component that is taking credit card numbers, which is often the case.)

To handle Backspace and cursor arrow keys, we have to store the original cursor position and restore it after editing from a spot anywhere other than the end of the string.

To enable handling of American Express, I use a variable called partitions to store the 4-6-5 spacing format for Amex and the 4-4-4-4 spacing format for all other cards. We loop partitions as we add spaces.

  /* Insert spaces to make CC number more legible */
  cardNumberSpacing() {
    const input = this.ccNumberField.nativeElement;
    const { selectionStart } = input;
    const { cardNumber } = this.paymentForm.controls;

    let trimmedCardNum = cardNumber.value.replace(/\s+/g, '');

    if (trimmedCardNum.length > 16) {
      trimmedCardNum = trimmedCardNum.substr(0, 16);
    }

     /* Handle American Express 4-6-5 spacing format */
    const partitions = trimmedCardNum.startsWith('34') || trimmedCardNum.startsWith('37') 
                       ? [4,6,5] 
                       : [4,4,4,4];

    const numbers = [];
    let position = 0;
    partitions.forEach(partition => {
      const part = trimmedCardNum.substr(position, partition);
      if (part) numbers.push(part);
      position += partition;
    })

    cardNumber.setValue(numbers.join(' '));

    /* Handle caret position if user edits the number later */
    if (selectionStart < cardNumber.value.length - 1) {
      input.setSelectionRange(selectionStart, selectionStart, 'none');
    }
  }


If you have a routine of your own to detect American Express numbers, use it. What I'm using here simply examines the first two digits and compares them to PAN/IIN standards.

Higher in your component, ensure you have the right imports:

import { ViewChild, ElementRef } from '@angular/core';

And:

  @ViewChild('ccNumber') ccNumberField: ElementRef;

And when you set up your form controls, do something like this so that spaces can be included in your regex pattern:

this.paymentForm = this.fb.group({
  cardNumber: ['', [Validators.required, Validators.pattern('^[ 0-9]*$';), Validators.minLength(17)]]
})

And finally, in your template, configure your element like this:

    <input maxlength="20"
        formControlName="cardNumber"
        type="tel"
        #ccNumber
        (keyup)="cardNumberSpacing()">

You should be good to go!

Tony Brasunas
  • 4,012
  • 3
  • 39
  • 51