Simple Angular mask directive
This directive does not create it's own value accessor - it simply reuses whatever element is using already and just hooks in.
Also it is fully abstracted off of the HTML implementation and so can be safely used in WebWorker and server side environment.
<input type="text" name="masked-value" ngModel appMask="000-AAA-0">
import { Directive, Injector, Input, OnInit } from '@angular/core';
import { NgControl } from '@angular/forms';
import { valueToFormat, unmaskValue } from './mask';
@Directive({
selector: '[appMask]',
})
export class MaskDirective implements OnInit {
@Input() appMask: string;
private control: NgControl;
private _lastMaskedValue = '';
constructor(
private injector: Injector,
) { }
ngOnInit() {
this.control = this.injector.get(NgControl);
if (!this.control || !this.control.valueAccessor) {
return;
}
const originalWriteVal = this.control.valueAccessor.writeValue.bind(this.control.valueAccessor);
this.control.valueAccessor.writeValue = (val: any) => originalWriteVal(this._maskValue(val));
const originalChange = (<any>this.control.valueAccessor)['onChange'].bind(this.control.valueAccessor);
this.control.valueAccessor.registerOnChange((val: any) => originalChange(this._unmaskValue(val)));
this._setVal(this._maskValue(this.control.value));
}
private _maskValue(val: string): string {
if (!this.appMask || val === this._lastMaskedValue) {
return val;
}
const maskedVal = this._lastMaskedValue =
valueToFormat(
val,
this.appMask, this._lastMaskedValue.length > val.length,
this._lastMaskedValue);
return maskedVal;
}
private _unmaskValue(val: string): string {
const maskedVal = this._maskValue(val);
const unmaskedVal = unmaskValue(maskedVal);
if (maskedVal !== val) {
this._setVal(maskedVal);
}
return maskedVal ? unmaskedVal : '';
}
private _setVal(val: string) {
if (this.control.control) {
this.control.control.setValue(val, { emitEvent: false });
}
}
}
import { StringHashMap } from '../../core/facade/types';
const _formatToRegExp: StringHashMap<RegExp> = {
'0': /[0-9]/, 'a': /[a-z]/, 'A': /[A-Z]/, 'B': /[a-zA-Z]/,
};
const _allFormatsStr = '(' +
Object.keys(_formatToRegExp)
.map(key => _formatToRegExp[key].toString())
.map(regexStr => regexStr.substr(1, regexStr.length - 2))
.join('|')
+ ')';
const _allFormatsGlobal = getAllFormatRegexp('g');
/**
* Apply format to a value string
*
* Format can be constructed from next symbols:
* - '0': /[0-9]/,
* - 'a': /[a-z]/,
* - 'A': /[A-Z]/,
* - 'B': /[a-zA-Z]/
*
* Example: 'AAA-00BB-aaaa'
* will accept 'COD-12Rt-efww'
*
* @param value Current value
* @param format Format
* @param goingBack Indicates if change was done by BackSpace
* @param prevValue Pass to precisely detect formatter chars
*/
export function valueToFormat(value: string, format: string, goingBack = false, prevValue?: string): string {
let maskedValue = '';
const unmaskedValue = unmaskValue(value);
const isLastCharFormatter = !getAllFormatRegexp().test(value[value.length - 1]);
const isPrevLastCharFormatter = prevValue && !getAllFormatRegexp().test(prevValue[prevValue.length - 1]);
let formatOffset = 0;
for (let i = 0, maxI = Math.min(unmaskedValue.length, format.length); i < maxI; ++i) {
const valueChar = unmaskedValue[i];
let formatChar = format[formatOffset + i];
let formatRegex = getFormatRegexp(formatChar);
if (formatChar && !formatRegex) {
maskedValue += formatChar;
formatChar = format[++formatOffset + i];
formatRegex = getFormatRegexp(formatChar);
}
if (valueChar && formatRegex) {
if (formatRegex.test(valueChar)) {
maskedValue += valueChar;
} else {
break;
}
}
const nextFormatChar = format[formatOffset + i + 1];
const nextFormatRegex = getFormatRegexp(nextFormatChar);
const isLastIteration = i === maxI - 1;
if (isLastIteration && nextFormatChar && !nextFormatRegex) {
if (!isLastCharFormatter && goingBack) {
if (prevValue && !isPrevLastCharFormatter) {
continue;
}
maskedValue = maskedValue.substr(0, formatOffset + i);
} else {
maskedValue += nextFormatChar;
}
}
}
return maskedValue;
}
export function unmaskValue(value: string): string {
const unmaskedMathes = value.match(_allFormatsGlobal);
return unmaskedMathes ? unmaskedMathes.join('') : '';
}
function getAllFormatRegexp(flags?: string) {
return new RegExp(_allFormatsStr, flags);
}
function getFormatRegexp(formatChar: string): RegExp | null {
return formatChar && _formatToRegExp[formatChar] ? _formatToRegExp[formatChar] : null;
}
I'm getting the following error:
Any idea why? I Just copied you code and added it into my app.
I'm using Ionic 4.7.0 and Angular 5.2.10