以Angular2formsinput掩码字段
是否有可能在Angular 2中使用模型驱动的表单,并find允许屏蔽input字段的指令,如电话号码条目(123) 123-4567
?
Plunker> = RC.5
原版的
你可以做的一个方法是使用一个指令,注入NgControl
并操纵该值
( 详情请见内嵌评论 )
@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示例<= RC.5
可以使用指令来完成。 下面是我build立的input掩码的蹲点。
https://plnkr.co/edit/hRsmd0EKci6rjGmnYFRr?p=preview
码:
import {Directive, Attribute, ElementRef, OnInit, OnChanges, Input, SimpleChange } from 'angular2/core'; import {NgControl, DefaultValueAccessor} from 'angular2/common'; @Directive({ selector: '[mask-input]', host: { //'(keyup)': 'onInputChange()', '(click)': 'setInitialCaretPosition()' }, inputs: ['modify'], providers: [DefaultValueAccessor] }) export class MaskDirective implements OnChanges { maskPattern: string; placeHolderCounts: any; dividers: string[]; modelValue: string; viewValue: string; intialCaretPos: any; numOfChar: any; @Input() modify: any; constructor(public model: NgControl, public ele: ElementRef, @Attribute("mask-input") maskPattern: string) { this.dividers = maskPattern.replace(/\*/g, "").split(""); this.dividers.push("_"); this.generatePattern(maskPattern); this.numOfChar = 0; } ngOnChanges(changes: { [propertyName: string]: SimpleChange }) { this.onInputChange(changes); } onInputChange(changes: { [propertyName: string]: SimpleChange }) { this.modelValue = this.getModelValue(); var caretPosition = this.ele.nativeElement.selectionStart; if (this.viewValue != null) { this.numOfChar = this.getNumberOfChar(caretPosition); } var stringToFormat = this.modelValue; if (stringToFormat.length < 10) { stringToFormat = this.padString(stringToFormat); } this.viewValue = this.format(stringToFormat); if (this.viewValue != null) { caretPosition = this.setCaretPosition(this.numOfChar); } this.model.viewToModelUpdate(this.modelValue); this.model.valueAccessor.writeValue(this.viewValue); this.ele.nativeElement.selectionStart = caretPosition; this.ele.nativeElement.selectionEnd = caretPosition; } generatePattern(patternString) { this.placeHolderCounts = (patternString.match(/\*/g) || []).length; for (var i = 0; i < this.placeHolderCounts; i++) { patternString = patternString.replace('*', "{" + i + "}"); } this.maskPattern = patternString; } format(s) { var formattedString = this.maskPattern; for (var i = 0; i < this.placeHolderCounts; i++) { formattedString = formattedString.replace("{" + i + "}", s.charAt(i)); } return formattedString; } padString(s) { var pad = "__________"; return (s + pad).substring(0, pad.length); } getModelValue() { var modelValue = this.model.value; if (modelValue == null) { return ""; } for (var i = 0; i < this.dividers.length; i++) { while (modelValue.indexOf(this.dividers[i]) > -1) { modelValue = modelValue.replace(this.dividers[i], ""); } } return modelValue; } setInitialCaretPosition() { var caretPosition = this.setCaretPosition(this.modelValue.length); this.ele.nativeElement.selectionStart = caretPosition; this.ele.nativeElement.selectionEnd = caretPosition; } setCaretPosition(num) { var notDivider = true; var caretPos = 1; for (; num > 0; caretPos++) { var ch = this.viewValue.charAt(caretPos); if (!this.isDivider(ch)) { num--; } } return caretPos; } isDivider(ch) { for (var i = 0; i < this.dividers.length; i++) { if (ch == this.dividers[i]) { return true; } } } getNumberOfChar(pos) { var num = 0; var containDividers = false; for (var i = 0; i < pos; i++) { var ch = this.modify.charAt(i); if (!this.isDivider(ch)) { num++; } else { containDividers = true; } } if (containDividers) { return num; } else { return this.numOfChar; } }
}
注意:还有一些错误。
我使用TextMaskModule从' angular2-text-mask '
我的分裂,但你可以得到的想法
使用NPM NodeJS的软件包
"dependencies": { "angular2-text-mask": "8.0.0",
HTML
<input *ngIf="column?.type =='areaCode'" type="text" [textMask]="{mask: areaCodeMask}" [(ngModel)]="areaCodeModel"> <input *ngIf="column?.type =='phone'" type="text" [textMask]="{mask: phoneMask}" [(ngModel)]="phoneModel">
内部组件
public areaCodeModel = ''; public areaCodeMask = ['(', /[1-9]/, /\d/, /\d/, ')']; public phoneModel = ''; public phoneMask = [/\d/, /\d/, /\d/, '-', /\d/, /\d/, /\d/, /\d/];
angular4+
我已经创build了一个通用指令 ,能够接收任何掩码 ,并能够根据该值dynamic定义掩码 :
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); } }
(记得在你的NgModule中声明)
掩码中的数字字符是9
所以你的掩码是(999) 999-9999
。 如果需要,可以更改NUMERIC
静态字段(例如,如果将其更改为0
,则掩码应该是(000) 000-0000
)。
该值用掩码显示,但没有掩码存储在组件字段(在我的情况下,这是理想的行为)。 您可以使用[spKeepMask]="true"
将其与掩码一起存储。
该指令接收实现MaskGenerator
接口的对象。
面具generator.interface.ts:
export interface MaskGenerator { generateMask: (value: string) => string; }
这样就可以根据这个值dynamic地定义掩码 (比如信用卡)。
我创build了一个function类来存储蒙版,但是您也可以直接在组件中指定它。
我-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; } }
然后,你可以在你的组件中使用它(使用spMaskValue
而不是ngModel
,但是如果不是一个被动的forms,就像下面的例子一样,不要使用ngModel
,只是为了不会因为在指令中注入NgControl
;在反应forms中,你不需要包含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>
(看看phone02
,看到多input1个数字后,掩码变化;同样,看看phone01
存储的phone01
是不是掩码)
我已经用正常的input以及ionic
input( ion-input
)对它进行了testing,它们都是react native的(使用formControlName
,而不使用formControl
)和非react native的forms。
没有必要重新发明轮子! 使用货币掩码 ,不像TextMaskModule ,这个工作与数字inputtypes,它是超级简单的configuration。 我发现,当我做了我自己的指令,我不得不继续转换之间的数字和string做计算。 节省自己的时间。 这是链接: