输入掩码

ben*_*siu 52 forms input angular

是否可以在Angular 2中使用模型驱动的表单并实现一个允许屏蔽input像电话号码条目这样的字段的指令(123) 123-4567

Gün*_*uer 72

Angular5和6:

angular 5和6推荐的方法是使用@HostBindings和@HostListeners而不是host属性

删除主机并添加 @HostListener

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

  @HostListener('keydown.backspace', ['$event'])
  keydownBackspace(event) {
    this.onInputChange(event.target.value, true);
  }
Run Code Online (Sandbox Code Playgroud)

在线工作stackblitz链接:https://angular6-phone-mask.stackblitz.io

Stackblitz代码示例:https://stackblitz.com/edit/angular6-phone-mask

官方文档链接https://angular.io/guide/attribute-directives#respond-to-user-initiated-events

Angular2和4:

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);       
  }
}
Run Code Online (Sandbox Code Playgroud)
@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: ['']
    })
  }
}
Run Code Online (Sandbox Code Playgroud)

Plunker示例<= RC.5

  • 我用过:this.model.control.setValue(); 设置模型值和this.model.valueAccessor.writeValue(); 设置视图值. (2认同)

T04*_*435 22

我认为最简单的解决方案是添加ngx-mask

npm i --save ngx-mask
Run Code Online (Sandbox Code Playgroud)

那么你可以做

<input type='text' mask='(000) 000-0000' >
Run Code Online (Sandbox Code Playgroud)

或者

<p>{{ phoneVar | mask: '(000) 000-0000' }} </p>
Run Code Online (Sandbox Code Playgroud)

  • 您可能需要使用这个:/sf/ask/3551038011/,这基本上表明将其导入到低于app模块的模块中是关键。 (2认同)

Luc*_*tto 13

Angular 4+

我已经创建了一个通用指令,能够接收任何掩码,并且还能够根据值动态定义掩码:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

(记得在你的NgModule中声明它)

掩码中的数字字符就是9掩码(999) 999-9999.如果需要,可以更改NUMERIC静态字段(例如,如果将其更改为0,则应更改掩码(000) 000-0000).

该值与掩码一起显示,但存储在没有掩码的组件字段中(在我的情况下,这是理想的行为).您可以使用掩码进行存储[spKeepMask]="true".

该指令接收实现该MaskGenerator接口的对象.

面具generator.interface.ts:

export interface MaskGenerator {
    generateMask: (value: string) => string;
}
Run Code Online (Sandbox Code Playgroud)

这样就可以根据值动态定义掩码(如信用卡).

我已经创建了一个实用类来存储蒙版,但您也可以直接在组件中指定它.

我-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;      
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以在你的组件中使用它(使用spMaskValue而不是ngModel,但如果不是一个反应形式,使用ngModel什么都没有,就像在下面的例子中,只是为了你不会收到没有提供者的错误,因为注入NgControl了指令;在反应形式中,您不需要包括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;
}
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

(看一看,phone02看到当你再输入1个数字时,掩码会改变;另外,看看存储的值phone01是否没有掩码)

我用普通输入和ionic输入(ion-input)测试了它,包括反应性(有formControlName,没有formControl)和非反应形式.


Dem*_*ave 9

我使用' angular2-text-mask '中的TextMaskModule来做到这一点

我的分裂,但你可以得到这个想法

使用NPM NodeJS打包

"dependencies": {
    "angular2-text-mask": "8.0.0",
Run Code Online (Sandbox Code Playgroud)

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"> 
Run Code Online (Sandbox Code Playgroud)

内部组件

public areaCodeModel = '';
public areaCodeMask = ['(', /[1-9]/, /\d/, /\d/, ')'];

public phoneModel = '';
public phoneMask = [/\d/, /\d/, /\d/, '-', /\d/, /\d/, /\d/, /\d/];
Run Code Online (Sandbox Code Playgroud)


Gur*_*urt 7

反应表格


请参阅Stackblitz

除了@GünterZöchbauer上面的答案,我尝试了如下,它似乎工作但我不确定它是否是一种有效的方式.

我使用valueChangesobservable通过订阅来监听被动形式的变更事件.对于退格的特殊处理,我data从订阅中获取并检查它userForm.value.phone(from [formGroup]="userForm").因为,在那一刻,数据变为新值,但后者由于尚未设置而引用之前的值.如果数据小于先前值,则用户应从输入中删除字符.在这种情况下,更改模式如下:

来自: newVal = newVal.replace(/^(\d{0,3})/, '($1)');

至 : newVal = newVal.replace(/^(\d{0,3})/, '($1');

否则,正如上面提到的GünterZöchbauer所示,无法识别非数字字符的删除,因为当我们从输入中删除括号时,数字仍然保持不变并再次添加来自模式匹配的括号.

控制器:

import { Component,OnInit } from '@angular/core';
import { FormGroup,FormBuilder,AbstractControl,Validators } from '@angular/forms';
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit{

  constructor(private fb:FormBuilder) { 
    this.createForm();
  }

  createForm(){
    this.userForm = this.fb.group({
      phone:['',[Validators.pattern(/^\(\d{3}\)\s\d{3}-\d{4}$/),Validators.required]],
    });
  }

  ngOnInit() {
   this.phoneValidate();
  }

  phoneValidate(){
    const phoneControl:AbstractControl = this.userForm.controls['phone'];

    phoneControl.valueChanges.subscribe(data => {
    /**the most of code from @Günter Zöchbauer's answer.*/

    /**we remove from input but: 
       @preInputValue still keep the previous value because of not setting.
    */
    let preInputValue:string = this.userForm.value.phone;
    let lastChar:string = preInputValue.substr(preInputValue.length - 1);

    var newVal = data.replace(/\D/g, '');
    //when removed value from input
    if (data.length < preInputValue.length) {

      /**while removing if we encounter ) character,
         then remove the last digit too.*/
      if(lastChar == ')'){
         newVal = newVal.substr(0,newVal.length-1); 
      }
      if (newVal.length == 0) {
        newVal = '';
      } 
      else if (newVal.length <= 3) {
        /**when removing, we change pattern match.
        "otherwise deleting of non-numeric characters is not recognized"*/
        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');
      }
    //when typed value in input
    } else{


    if (newVal.length == 0) {
      newVal = '';
    } 
    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');
    }

  }
    this.userForm.controls['phone'].setValue(newVal,{emitEvent: false});
  });
 }

}
Run Code Online (Sandbox Code Playgroud)

模板:

<form [formGroup]="userForm"  novalidate>
  <div class="form-group">
    <label for="tel">Tel:</label>
    <input id="tel" formControlName="phone" maxlength="14">
  </div>
  <button [disabled]="userForm.status == 'INVALID'" type="submit">Send</button>
</form>
Run Code Online (Sandbox Code Playgroud)

UPDATE

有没有办法在字符串中间退格时保留光标位置?目前,它跳回到最后.

定义id <input id="tel" formControlName="phone" #phoneRef>renderer2#selectRootElement以获取组件中的本机元素.

所以我们可以使用以下方法获取光标位置

let start = this.renderer.selectRootElement('#tel').selectionStart;
let end = this.renderer.selectRootElement('#tel').selectionEnd;
Run Code Online (Sandbox Code Playgroud)

然后我们可以在输入更新为新值后应用它:

this.userForm.controls['phone'].setValue(newVal,{emitEvent: false});
//keep cursor the appropriate position after setting the input above.
this.renderer.selectRootElement('#tel').setSelectionRange(start,end);
Run Code Online (Sandbox Code Playgroud)

更新2

将这种逻辑放在指令而不是组件中可能更好

我还将逻辑放入指令中.这样可以更轻松地将其应用于其他元素.

请参阅Stackblitz

注意:它特定于(123) 123-4567模式.

  • 有没有办法在字符串中间退格时保留光标位置?目前,它跳回到最后. (2认同)