Mask for an Input to allow phone numbers? Mask for an Input to allow phone numbers? angular angular

Mask for an Input to allow phone numbers?


Angular5 and 6:

angular 5 and 6 recommended way is to use @HostBindings and @HostListeners instead of the host property

remove host and add @HostListener

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

Working Online stackblitz Link: https://angular6-phone-mask.stackblitz.io

Stackblitz Code example: https://stackblitz.com/edit/angular6-phone-mask

Official documentation link https://angular.io/guide/attribute-directives#respond-to-user-initiated-events

Angular2 and 4:

Plunker >= RC.5

original

One way you could do it is using a directive that injects NgControl and manipulates the value

(for details see inline comments)

@Directive({  selector: '[ngModel][phone]',  host: {    '(ngModelChange)': 'onInputChange($event)',    '(keydown.backspace)': 'onInputChange($event.target.value, true)'  }})export class PhoneMask {  constructor(public model: NgControl) {}  onInputChange(event, backspace) {    // remove all mask characters (keep only numeric)    var newVal = event.replace(/\D/g, '');    // special handling of backspace necessary otherwise    // deleting of non-numeric characters is not recognized    // this laves room for improvement for example if you delete in the     // middle of the string    if (backspace) {      newVal = newVal.substring(0, newVal.length - 1);    }     // don't show braces for empty value    if (newVal.length == 0) {      newVal = '';    }     // don't show braces for empty groups at the end    else if (newVal.length <= 3) {      newVal = newVal.replace(/^(\d{0,3})/, '($1)');    } else if (newVal.length <= 6) {      newVal = newVal.replace(/^(\d{0,3})(\d{0,3})/, '($1) ($2)');    } else {      newVal = newVal.replace(/^(\d{0,3})(\d{0,3})(.*)/, '($1) ($2)-$3');    }    // set the new value    this.model.valueAccessor.writeValue(newVal);         }}
@Component({  selector: 'my-app',  providers: [],  template: `  <form [ngFormModel]="form">    <input type="text" phone [(ngModel)]="data" ngControl="phone">   </form>  `,  directives: [PhoneMask]})export class App {  constructor(fb: FormBuilder) {    this.form = fb.group({      phone: ['']    })  }}

Plunker example <= RC.5


I Think the simplest solutions is to add ngx-mask

npm i --save ngx-mask

then you can do

<input type='text' mask='(000) 000-0000' >

OR

<p>{{ phoneVar | mask: '(000) 000-0000' }} </p>


Angular 4+

I've created a generic directive, able to receive any mask and also able to define the mask dynamically based on the value:

mask.directive.ts:

import { Directive, EventEmitter, HostListener, Input, Output } from '@angular/core';import { NgControl } from '@angular/forms';import { MaskGenerator } from '../interfaces/mask-generator.interface';@Directive({    selector: '[spMask]' })export class MaskDirective {    private static readonly ALPHA = 'A';    private static readonly NUMERIC = '9';    private static readonly ALPHANUMERIC = '?';    private static readonly REGEX_MAP = new Map([        [MaskDirective.ALPHA, /\w/],        [MaskDirective.NUMERIC, /\d/],        [MaskDirective.ALPHANUMERIC, /\w|\d/],    ]);    private value: string = null;    private displayValue: string = null;    @Input('spMask')     public maskGenerator: MaskGenerator;    @Input('spKeepMask')     public keepMask: boolean;    @Input('spMaskValue')     public set maskValue(value: string) {        if (value !== this.value) {            this.value = value;            this.defineValue();        }    };    @Output('spMaskValueChange')     public changeEmitter = new EventEmitter<string>();    @HostListener('input', ['$event'])    public onInput(event: { target: { value?: string }}): void {        let target = event.target;        let value = target.value;        this.onValueChange(value);    }    constructor(private ngControl: NgControl) { }    private updateValue(value: string) {        this.value = value;        this.changeEmitter.emit(value);        MaskDirective.delay().then(            () => this.ngControl.control.updateValueAndValidity()        );    }    private defineValue() {        let value: string = this.value;        let displayValue: string = null;        if (this.maskGenerator) {            let mask = this.maskGenerator.generateMask(value);            if (value != null) {                displayValue = MaskDirective.mask(value, mask);                value = MaskDirective.processValue(displayValue, mask, this.keepMask);            }           } else {            displayValue = this.value;        }        MaskDirective.delay().then(() => {            if (this.displayValue !== displayValue) {                this.displayValue = displayValue;                this.ngControl.control.setValue(displayValue);                return MaskDirective.delay();            }        }).then(() => {            if (value != this.value) {                return this.updateValue(value);            }        });    }    private onValueChange(newValue: string) {        if (newValue !== this.displayValue) {            let displayValue = newValue;            let value = newValue;            if ((newValue == null) || (newValue.trim() === '')) {                value = null;            } else if (this.maskGenerator) {                let mask = this.maskGenerator.generateMask(newValue);                displayValue = MaskDirective.mask(newValue, mask);                value = MaskDirective.processValue(displayValue, mask, this.keepMask);            }            this.displayValue = displayValue;            if (newValue !== displayValue) {                this.ngControl.control.setValue(displayValue);            }            if (value !== this.value) {                this.updateValue(value);            }        }    }    private static processValue(displayValue: string, mask: string, keepMask: boolean) {        let value = keepMask ? displayValue : MaskDirective.unmask(displayValue, mask);        return value    }    private static mask(value: string, mask: string): string {        value = value.toString();        let len = value.length;        let maskLen = mask.length;        let pos = 0;        let newValue = '';        for (let i = 0; i < Math.min(len, maskLen); i++) {            let maskChar = mask.charAt(i);            let newChar = value.charAt(pos);            let regex: RegExp = MaskDirective.REGEX_MAP.get(maskChar);            if (regex) {                pos++;                if (regex.test(newChar)) {                    newValue += newChar;                } else {                    i--;                    len--;                }            } else {                if (maskChar === newChar) {                    pos++;                } else {                    len++;                }                newValue += maskChar;            }        }               return newValue;    }    private static unmask(maskedValue: string, mask: string): string {        let maskLen = (mask && mask.length) || 0;        return maskedValue.split('').filter(            (currChar, idx) => (idx < maskLen) && MaskDirective.REGEX_MAP.has(mask[idx])        ).join('');    }    private static delay(ms: number = 0): Promise<void> {        return new Promise(resolve => setTimeout(() => resolve(), ms)).then(() => null);    }}

(Remember to declare it in your NgModule)

The numeric character in the mask is 9 so your mask would be (999) 999-9999. You can change the NUMERIC static field if you want (if you change it to 0, your mask should be (000) 000-0000, for example).

The value is displayed with mask but stored in the component field without mask (this is the desirable behaviour in my case). You can make it be stored with mask using [spKeepMask]="true".

The directive receives an object that implements the MaskGenerator interface.

mask-generator.interface.ts:

export interface MaskGenerator {    generateMask: (value: string) => string;}

This way it's possible to define the mask dynamically based on the value (like credit cards).

I've created an utilitarian class to store the masks, but you can specify it directly in your component too.

my-mask.util.ts:

export class MyMaskUtil {    private static PHONE_SMALL = '(999) 999-9999';    private static PHONE_BIG = '(999) 9999-9999';    private static CPF = '999.999.999-99';    private static CNPJ = '99.999.999/9999-99';    public static PHONE_MASK_GENERATOR: MaskGenerator = {        generateMask: () =>  MyMaskUtil.PHONE_SMALL,    }    public static DYNAMIC_PHONE_MASK_GENERATOR: MaskGenerator = {        generateMask: (value: string) => {            return MyMaskUtil.hasMoreDigits(value, MyMaskUtil.PHONE_SMALL) ?                 MyMaskUtil.PHONE_BIG :                 MyMaskUtil.PHONE_SMALL;        },    }    public static CPF_MASK_GENERATOR: MaskGenerator = {        generateMask: () => MyMaskUtil.CPF,    }    public static CNPJ_MASK_GENERATOR: MaskGenerator = {        generateMask: () => MyMaskUtil.CNPJ,    }    public static PERSON_MASK_GENERATOR: MaskGenerator = {        generateMask: (value: string) => {            return MyMaskUtil.hasMoreDigits(value, MyMaskUtil.CPF) ?                 MyMaskUtil.CNPJ :                 MyMaskUtil.CPF;        },    }    private static hasMoreDigits(v01: string, v02: string): boolean {        let d01 = this.onlyDigits(v01);        let d02 = this.onlyDigits(v02);        let len01 = (d01 && d01.length) || 0;        let len02 = (d02 && d02.length) || 0;        let moreDigits = (len01 > len02);        return moreDigits;          }    private static onlyDigits(value: string): string {        let onlyDigits = (value != null) ? value.replace(/\D/g, '') : null;        return onlyDigits;          }}

Then you can use it in your component (use spMaskValue instead of ngModel, but if is not a reactive form, use ngModel with nothing, like in the example below, just so that you won't receive an error of no provider because of the injected NgControl in the directive; in reactive forms you don't need to include ngModel):

my.component.ts:

@Component({ ... })export class MyComponent {    public phoneValue01: string = '1231234567';    public phoneValue02: string;    public phoneMask01 = MyMaskUtil.PHONE_MASK_GENERATOR;    public phoneMask02 = MyMaskUtil.DYNAMIC_PHONE_MASK_GENERATOR;}

my.component.html:

<span>Phone 01 ({{ phoneValue01 }}):</span><br><input type="text" [(spMaskValue)]="phoneValue01" [spMask]="phoneMask01" ngModel><br><br><span>Phone 02 ({{ phoneValue02 }}):</span><br><input type="text" [(spMaskValue)]="phoneValue02" [spMask]="phoneMask02" [spKeepMask]="true" ngModel>

(Take a look at phone02 and see that when you type 1 more digit, the mask changes; also, look that the value stored of phone01 is without mask)

I've tested it with normal inputs as well as with ionic inputs (ion-input), with both reactive (with formControlName, not with formControl) and non-reactive forms.