1

I want to mask my input value, basically I have an input field for credit card expiry date and want to mask it in format mm/yy, this is what I had tried :

input-mask.directive.ts

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

@Directive({
  selector: '[formControlName][appInputMask]',
})
export class InputMaskDirective {

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

    const trimmed = input.value.replace(/\s+/g, '').slice(0, 4);
    if (trimmed.length > 3) {
      return (input.value = `${trimmed.slice(0, 2)}/${trimmed.slice(2)}`);
    }
  }
}

I got my expected output, but the problem is when using backspace on input field, it messed up, here is my demo on stackblitz, how to solve this? or is there any better approach on input mask?

3 Answers 3

3

The problems

  1. HTMLInput element's maxlength should be 5 otherwise it won't allow a user to add 4 digits once you programmatically add a slash in it.

  2. The same issue exist in const trimmed = input.value.replace(/\s+/g, '').slice(0,4); If it contains a slash, the slice should be end at 5 instead of 4.

  3. In return statement

    return (input.value = `${trimmed.slice(0, 2)}/${trimmed.slice(2)}`);
    

If the slash is added once, the ${trimmed.slice(2) will return /\d{1,2}. So you need to avoid the slash and start from 3.

The solution

change max length to 5 from 4

 <input ... maxlength="5">

Now, input-mask.directive.ts need some changes

Change

const trimmed = input.value.replace(/\s+/g, '').slice(0,4);

to

const trimmed = input.value.replace(/\s+/g, '').slice(0, input.value.indexOf('/')==-1?4:5);

Because when you add /, the length will become 5.

Don't use .slice(0) because it allows a user to paste 5 digits and when you add a slash, it'll become 6. So the date will look like 11/111 instead of 11/11

Change

return (input.value = `${trimmed.slice(0, 2)}/${trimmed.slice(2)}`);

to

return (input.value = `${trimmed.slice(0, 2)}/${trimmed.slice(trimmed.indexOf('/')==-1?2:3)}`);

This is where your code getting messed up. If it contains a slash, then you should slice it from 3. Else slice it from 2.

final code of HostListener in input-mask directive

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

  const trimmed = input.value.replace(/\s+/g, '').slice(0, input.value.indexOf('/')==-1?4:5);
  if (trimmed.length > 3) {
    return (input.value = `${trimmed.slice(0, 2)}/${trimmed.slice(trimmed.indexOf('/')==-1?2:3)}`);
  }
}

Working example : stackblitz

Sign up to request clarification or add additional context in comments.

3 Comments

I though special characters are not included, thats why I add maxLength = 4,
This doesn't filter months. For example, if you enter 8888 the output is 88/88
Please share a working example with the month filter.
0

Use ngx-mask will make your work easy by just giving pattern for date, card, etc.

<input type='text' mask='99/99' >

Here is stackblitz demo

2 Comments

good library, is there any solution using directive as my question ?
This doesn't filter months. For example, if you enter 8888 the output is 88/88
0

use below code . Create directive .ts file and add below code

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

@Directive({
    selector: '[formControlName][inputDateMask]',
})
export class InputMaskDirective {
    constructor(public ngControl: NgControl) { }

    @HostListener('ngModelChange', ['$event'])
    onModelChange(event: any) {
        this.onInputChange(event, false);
    }

    @HostListener('keydown.backspace', ['$event'])
    keydownBackspace(event: any) {
        this.onInputChange(event.target.value, true);
    }

    onInputChange(event: any, backspace: any) {
        let newDate = '';
     
        let month: string = "";
        let day: string = "";
        let year: string = "";

        if (event)
            newDate = event.replace(/\D/g, '');

        if (backspace) {
            newDate = newDate.substring(0, newDate.length - 1);
        }
        if (newDate.length > 7) {

            if(newDate.match(/^[0-9]+$/) ? true : false)
            month = newDate.substring(0, 2);
            day = newDate.substring(2, 4);
            year = newDate.substring(4, 8);
            newDate = (`${month}/${day}/${year}`);

            this.ngControl?.valueAccessor?.writeValue(newDate);
        }
    }
}

then declare directive in app.module.ts file

declarations: [
 InputMaskDirective
],

exports: [
  InputMaskDirective
]

use directive like this

<input matInput placeholder="MM/DD/YYYY" [maxlength]="10" inputDateMask formControlName="dateOfBirth" />

hope this help, thank you

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.