Skip to content

Instantly share code, notes, and snippets.

@JoeMeeks
Last active October 25, 2020 13:44
Show Gist options
  • Save JoeMeeks/e7932fa291ba1c7e1e66597e5b52c633 to your computer and use it in GitHub Desktop.
Save JoeMeeks/e7932fa291ba1c7e1e66597e5b52c633 to your computer and use it in GitHub Desktop.
Custom Ionic 2 & 3 Input Mask Directive
<ion-input type="tel" pattern="\d*" placeholder="(xxx) xxx-xxxx" mask="(***) ***-****" [(ngModel)]="phone" name="phone"></ion-input>
<ion-input type="tel" pattern="\d*" placeholder="xxx-xx-xxxx" mask="***-**-****" [(ngModel)]="ssn" name="ssn"></ion-input>
import { Directive, Attribute } from '@angular/core';
@Directive({
selector: '[mask]',
host: {
'(keyup)': 'onInputChange($event)'
}
})
export class InputMask {
pattern: string;
constructor(
@Attribute('mask') pattern: string
) {
this.pattern = pattern;
}
onInputChange(e) {
try {
let value = e.target.value,
caret = e.target.selectionStart,
pattern = this.pattern,
reserve = pattern.replace(/\*/, 'g'),
applied = '',
ordinal = 0;
if (e.keyCode === 8 || e.key === 'Backspace' || e.keyCode === 46 || e.key === 'Delete') {
if (value.length) {
//remove all trailing formatting
while (value.length && pattern[value.length] && pattern[value.length] !== '*') {
value = value.substring(0, value.length - 1);
}
//remove all leading formatting to restore placeholder
if (pattern.substring(0, value.length).indexOf('*') < 0) {
value = value.substring(0, value.length - 1);
}
}
}
//apply mask characters
for (var i = 0; i < value.length; i++) {
//enforce pattern limit
if (i < pattern.length) {
//match mask
if (value[i] === pattern[ordinal]) {
applied += value[i];
ordinal++;
} else if (reserve.indexOf(value[i]) > -1) {
//skip other reserved characters
} else {
//apply leading formatting
while (ordinal < pattern.length && pattern[ordinal] !== '*') {
applied += pattern[ordinal];
ordinal++;
}
applied += value[i];
ordinal++;
//apply trailing formatting
while (ordinal < pattern.length && pattern[ordinal] !== '*') {
applied += pattern[ordinal];
ordinal++;
}
}
}
}
e.target.value = applied;
if (caret < value.length) {
e.target.setSelectionRange(caret, caret);
}
} catch (ex) {
console.error(ex.message);
}
}
}
@coicoronado
Copy link

coicoronado commented Jun 5, 2017

Hi i've found your directive very usefull but i have to make a little quirk when i'm using formBuilder for ionic 2 just to change the line 57 to "event.target.value=value;" so the form builder can handle the change of the value instead of the use of the ngmodel

@luckylooke
Copy link

DOES NOT WORK FOR ME :/

Using:

<ion-input placeholder="Manuálne zadanie kódu"
					type="number"
					pattern="\d{6}-\d{4}-\d{6}"
					mask="******-****-******"
					formControlName="serialNumber">
				</ion-input>

tried also with

pattern="\d*"

Typing 7th number clears the input.
Warning occures in console:
The specified value "135135-" is not a valid number. The value must match to the following regular expression: -?(\d+|\d+\.\d+|\.\d+)([eE][-+]?\d+)?

Tested also with @coicoronado update, because I am using form builder.

@luckylooke
Copy link

luckylooke commented Aug 21, 2017

OK, removing type="number" solves the warning.. now directive seems to work, but id does not follow the pattern correctly. It apllies only one dash - in pattern and on incorrect position :/ Example 18616811151-51351135135151

@luckylooke
Copy link

luckylooke commented Aug 21, 2017

Even when I try without formBuilder it is buggy, sometimes it mask the value and sometimes it does not.. :/

ionic info:

cli packages: (/Users/lucky/Documents/projects/tatramat/client/node_modules)

    @ionic/cli-plugin-cordova       : 1.6.2
    @ionic/cli-plugin-ionic-angular : 1.4.1
    @ionic/cli-utils                : 1.7.0
    ionic (Ionic CLI)               : 3.7.0

global packages:

    Cordova CLI : 7.0.1 

local packages:

    @ionic/app-scripts : 1.2.5
    Cordova Platforms  : android 6.2.3 browser 4.1.0 ios 4.4.0
    Ionic Framework    : ionic-angular 2.3.0

System:

    Node       : v6.9.5
    OS         : macOS Sierra
    Xcode      : Xcode 8.3.3 Build version 8E3004b 
    ios-deploy : 1.9.1 
    ios-sim    : 5.0.13 
    npm        : 5.3.0 

@niravjadatiya
Copy link

Great job it's working for me
thanks
and yes @coicoronado thanks to you also

@JoeMeeks
Copy link
Author

JoeMeeks commented Oct 5, 2017

@coicoronado, @luckylooke, @niravjadatiya This has been updated to fix a critical Delete key input bug. Also the code is now far more efficient, performant, and has the added behavior to retain caret position.

@mwiley63
Copy link

@JoeMeeks this directive works great, thanks for the code!

@adsonrocha
Copy link

It doesn't work with input type="number".

@parastaneja
Copy link

It doesn't work with input type='number'

@BlacksmithVRS
Copy link

Thank you a lot for this!

@BlacksmithVRS
Copy link

Quick question to @JoeMeeks : I have a field which can hold 2 types of values, depending on a radio button clicked by the user. How can I change the mask based on the type chosen by the user, realtime?

@codinronan
Copy link

@JoeMeeks this is really cool! Since you can safely assume this directive is only tied to an input component, you can do something like this to ensure that the mask gets applied on first render, as well:

https://gist.github.com/codinronan/d6ddfc4ab8dba2277386dbed160b050e

@JoeMeeks
Copy link
Author

@BlacksmithVRS my GitHub notification settings were not letting me know about mentions so I apologize for the late reply. Have you tried:

<ion-input type="tel" pattern="\d*" placeholder="(xxx) xxx-xxxx" [mask]="publicMaskVar" [(ngModel)]="phone" name="phone"></ion-input>

with publicMaskVar being assigned with the (ionChange) handler of your radio input?

@JoeMeeks
Copy link
Author

@codinconan nice ngAfterViewInit implementation!

@mapplics
Copy link

thanks man! you saved my day!

@jscarl
Copy link

jscarl commented Sep 3, 2018

@JoeMeeks nice work, how to use this directive for thousand separator?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment