tipos tamaño que poner formularios formulario form envio dividir componentes como codigos borde angular forms input mask

angular - tamaño - ¿Máscara para una entrada para permitir números de teléfono?



tamaño de formulario html (9)

Angular 4+

Creé una directiva genérica , capaz de recibir cualquier máscara y también capaz de definir la máscara dinámicamente en función del valor:

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); } }

(Recuerde declararlo en su NgModule)

El carácter numérico en la máscara es 9 por lo que su máscara sería (999) 999-9999 . Puede cambiar el campo estático NUMERIC si lo desea (si lo cambia a 0 , su máscara debería ser (000) 000-0000 , por ejemplo).

El valor se muestra con máscara pero se almacena en el campo componente sin máscara (este es el comportamiento deseable en mi caso). Puede hacer que se almacene con máscara usando [spKeepMask]="true" .

La directiva recibe un objeto que implementa la interfaz MaskGenerator .

mask-generator.interface.ts:

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

De esta manera, es posible definir la máscara dinámicamente en función del valor (como las tarjetas de crédito).

He creado una clase utilitaria para almacenar las máscaras, pero también puede especificarla directamente en su componente.

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; } }

Luego puede usarlo en su componente (use spMaskValue lugar de ngModel , pero si no es una forma reactiva, use ngModel sin nada, como en el ejemplo a continuación, solo para que no reciba un error de ningún proveedor debido al NgControl inyectado en la directiva; en formas reactivas no necesita incluir 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>

(Eche un vistazo a phone02 y vea que cuando escribe 1 dígito más, la máscara cambia; también, observe que el valor almacenado de phone01 es sin máscara)

Lo probé con entradas normales, así como con entradas ionic ( ion-input ), tanto con formControlName reactivas (con formControlName , no con formControl ) como no reactivas.

¿Es posible tener una forma basada en modelos en Angular 2 e implementar una directiva que permita enmascarar un campo de input como una entrada de número de teléfono (123) 123-4567 ?


Forma reactiva

Ver en Stackblitz

Además de la respuesta anterior de @ Günter Zöchbauer , intenté lo siguiente y parece estar funcionando, pero no estoy seguro de si es una forma eficiente.

Utilizo valueChanges observable para escuchar eventos de cambio en forma reactiva suscribiéndome a él. Para un manejo especial del retroceso, obtengo los data de la suscripción y los userForm.value.phone(from [formGroup]="userForm") con userForm.value.phone(from [formGroup]="userForm") . Porque, en ese momento, los datos cambian al nuevo valor, pero este último se refiere al valor anterior debido a que aún no se ha configurado. Si los datos son inferiores al valor anterior, el usuario debe eliminar el carácter de la entrada. En este caso, cambie el patrón de la siguiente manera:

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

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

De lo contrario, como Günter Zöchbauer mencionó anteriormente, no se reconoce la eliminación de caracteres no numéricos porque cuando eliminamos paréntesis de la entrada, los dígitos siguen siendo los mismos y se agregan nuevamente paréntesis de la coincidencia de patrones.

Controlador:

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}); }); } }

Modelo:

<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>

ACTUALIZAR

¿Hay alguna manera de preservar la posición del cursor mientras retrocede en el medio de la cadena? Actualmente, salta de regreso al final.

Defina un id <input id="tel" formControlName="phone" #phoneRef> y renderer2#selectRootElement para obtener el elemento nativo en el componente.

Entonces podemos obtener la posición del cursor usando:

let start = this.renderer.selectRootElement(''#tel'').selectionStart; let end = this.renderer.selectRootElement(''#tel'').selectionEnd;

y luego podemos aplicarlo después de que la entrada se actualice al nuevo valor:

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);

ACTUALIZACIÓN 2

Probablemente sea mejor poner este tipo de lógica dentro de una directiva en lugar de en el componente

También puse la lógica en una directiva. Esto facilita su aplicación a otros elementos.

Ver en Stackblitz

Nota: es específico del patrón (123) 123-4567 .


¡No hay necesidad de reinventar la rueda! Use Currency Mask , a diferencia de TextMaskModule , este funciona con el tipo de entrada de número y es muy fácil de configurar. Cuando hice mi propia directiva, tuve que seguir convirtiendo entre número y cadena para hacer cálculos. Ahórrate el tiempo. Aquí está el enlace:

https://github.com/cesarrew/ng2-currency-mask


Combinando la respuesta de Günter Zöchbauer con el viejo vanilla-JS , aquí hay una directiva con dos líneas de lógica que admite el formato (123) 456-7890 .

Formas reactivas: Plunk

import { Directive, Output, EventEmitter } from "@angular/core"; import { NgControl } from "@angular/forms"; @Directive({ selector: ''[formControlName][phone]'', host: { ''(ngModelChange)'': ''onInputChange($event)'' } }) export class PhoneMaskDirective { @Output() rawChange:EventEmitter<string> = new EventEmitter<string>(); constructor(public model: NgControl) {} onInputChange(value) { var x = value.replace(//D/g, '''').match(/(/d{0,3})(/d{0,3})(/d{0,4})/); var y = !x[2] ? x[1] : ''('' + x[1] + '') '' + x[2] + (x[3] ? ''-'' + x[3] : ''''); this.model.valueAccessor.writeValue(y); this.rawChange.emit(rawValue); } }

Formas basadas en plantillas : Plunk

import { Directive } from "@angular/core"; import { NgControl } from "@angular/forms"; @Directive({ selector: ''[ngModel][phone]'', host: { ''(ngModelChange)'': ''onInputChange($event)'' } }) export class PhoneMaskDirective { constructor(public model: NgControl) {} onInputChange(value) { var x = value.replace(//D/g, '''').match(/(/d{0,3})(/d{0,3})(/d{0,4})/); value = !x[2] ? x[1] : ''('' + x[1] + '') '' + x[2] + (x[3] ? ''-'' + x[3] : ''''); this.model.valueAccessor.writeValue(value); } }


Creo que la solución más simple es agregar ngx-mask

npm i --save ngx-mask

entonces puedes hacer

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

O

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


Hago esto usando TextMaskModule de '' angular2-text-mask ''

Los míos están divididos pero puedes entender la idea

Paquete usando 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">

Componente interior

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


Se puede hacer usando una directiva. A continuación se muestra el plunker de la máscara de entrada que construí.

https://plnkr.co/edit/hRsmd0EKci6rjGmnYFRr?p=preview

Código:

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; } }

}

Nota: todavía hay algunos errores.



Angular5 y 6:

la forma recomendada angular 5 y 6 es usar @HostBindings y @HostListeners en lugar de la propiedad del host

eliminar host y agregar @HostListener

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

Trabajando en línea stackblitz Link: https://angular6-phone-mask.stackblitz.io

Ejemplo de código de Stackblitz: https://stackblitz.com/edit/angular6-phone-mask

Enlace de documentación oficial https://angular.io/guide/attribute-directives#respond-to-user-initiated-events

Angular2 y 4:

Plunker> = RC.5

original

Una forma de hacerlo es usando una directiva que inyecta NgControl y manipula el valor

( para más detalles ver comentarios en línea )

@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: [''''] }) } }

Ejemplo de Plunker <= RC.5