前面通过设定FromGroup
的updateOn
配置,将表单的验证时机修改为了当输入框输入焦点之后,这个操作引发的问题是,输入限制不起作用了!比如有些地方我只需要输入数字,不可以输入其他字符,做这个输入限制是靠在验证器里进行replace,现在是当鼠标遗失后才去验证,也就是鼠标遗失后采取replace,很糟心啊,得想想解决办法。
既然不能用验证器限制输入,那么我们使用input
方法来进行监听和替换怎么样?看方法:
1 2 3 4 5 6 7 8 9 10 11 12
|
onInput($event) { if (!$event) { return; } const target = $event.target; const regexp = /[^0-9]/ig; target.value = target.value.replace(regexp, ''); }
|
这个方法通过使用正则表达式,将不是数字的字符都替换掉,这样应该可以满足我们的限制输入。
应用在表单上:
1 2 3 4 5 6
| <input nz-input name="code" id="code" (input)="onInput($event)" formControlName="code" >
|
然后运行测试,发现这种情况输入是可以限制的,但是,尽管页面上显示的是replace后的,FormControl
拿到的还是格式之前的,也就是假如我输入一串数字,再输入一串字母,表单拿到的值是数字+最后一个字母,而并不是只有数字。
而且,当我切换到中文输入法的时候,更会发生错乱。为什么呢?因为输入汉字的时候,有个中间状态,也就是我不断输入并选词的状态,这个时候虽然界面上是在选词,但是input
事件能监听到,还会替换掉,所以页面上就乱套了。需要避免这种情况。
解决中文输入的问题
需要解决中文输入的问题,那就要在用户继续输入的时候,不要进行替换,在输入完成的时候进行替换。这有个中间状态。
根据W3C的规范,在我们开始输入法输入词组的时候,浏览器会触发一个compositionstart
事件,在输入法结束的时候,浏览器会触发compositionend
事件,我们只需要维护一个变量,通过监听状态来改变这个状态,就可以知道什么时候输入完成。看代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| isComposite: boolean = false; onInput($event) { if (!$event || this.isComposite) { return; } const target = $event.target; const regexp = /[^0-9]/ig; target.value = target.value.replace(regexp, ''); } onCompositionStart() { this.isComposite = true; } onCompositionEnd($event) { this.isComposite = false; this.onInput($event); }
|
相应的html:
1 2 3 4 5 6 7 8
| <input nz-input name="code" id="code" (compositionstart)="onCompositionStart()" (compositionend)="onCompositionEnd($event)" (input)="onInput($event)" formControlName="code" >
|
这样我们可以正常输入中文,并在中文输入完成后完成对文字的replace。
目前为止,表面上的输入限制是做好的,但是表单拿到的值还是有杂质的。需要进一步处理值。
处理表单值
现在当我们输入数字+其他字符的时候,使输入框遗失焦点,发现表单得到的值总是有最后一个字母保留,输入中文的话后续的全部保留。很纳闷。我们需要去掉这个尾巴。
现在我们的表单改成了输入框遗失焦点后才验证,那么我们能不能在输入框遗失焦点后对值再手动更新一下呢?
1 2 3 4
| onBlur($event) { this.onInput($event); this.formGroup.controls['code'].setValue($event.target.value); }
|
在html中加入blur:
1 2 3 4 5 6 7 8 9
| <input nz-input name="code" id="code" (compositionstart)="onCompositionStart()" (compositionend)="onCompositionEnd($event)" (input)="onInput($event)" (blur)="onBlur($event)" formControlName="code" >
|
在页面中测试,发现确实是能用。至少,目前是满足使用的。
但是,我们几乎每个表单都要这样改,四个方法,页面上进行配置,主要是还公用一个变量isComposite
,这样不仅很累,而且很容易出问题。我们需要出去为一个指令。
限制输入-指令
上面各种方法移入指令中倒也不是怎么麻烦,麻烦的是如何在指令中去修改FormGroup
里面FormControl
的值?
需要祭出NgControl
这个类了,NgControl
是一个基类,所有的控制指令都继承于它。它可以给一个Dom元素绑定一个FormControl
对象。在Angular表单内部使用。还是想啥来啥,注入了这个NgControl
,我们就有了在指令里面操作FormControl
的能力了。
来看看NgControl
,它继承了一个AbstractControlDirective
,这个才是真正具有FormControl
的源头。
直接来看指令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
| import {Directive, ElementRef, HostListener, Input} from '@angular/core'; import {NgControl} from '@angular/forms';
@Directive({ selector: '[appLimitInput]' }) export class LimitInputDirective {
@Input() replace: string|RegExp; _regexp: RegExp; isComposite: boolean = false;
@Input() replaceFn: Function; constructor( private el: ElementRef, private control: NgControl, ) { }
@HostListener('compositionstart') onCompositionStart() { this.isComposite = true; }
@HostListener('compositionend', ['$event']) onCompositionEnd($event) { this.isComposite = false; this.limitInput($event); } @HostListener('change') onChange() { this.control.control.setValue(this.el.nativeElement.value); }
@HostListener('blur', ['$event']) onBlur($event) { this.control.control.setValue(this.el.nativeElement.value); }
@HostListener('input', ['$event']) onInput($event) { this.limitInput($event); } private limitInput($event) { if (!this.el.nativeElement.value || this.isComposite) { return; } if (this.replaceFn) { this.replaceFn(this.el); return; } else { this._regexp = this.getRegexp(this.replace); this.el.nativeElement.value = this.el.nativeElement.value.replace(this._regexp, ''); } }
private getRegexp(str) { if (typeof str === 'object') { return str; } let regexp; switch (str) { case 'en': regexp = /(^\s|[\u4e00-\u9fa5])/g; break; case 'number': regexp = /[^0-9]/ig; break; case 'number|letter': regexp = /[^\d|\w]/ig; break; case 'float': this.replace = /[^\d|\.]/ig; break; } return regexp; } }
|
在使用的时候,可以传入内置的正则替换的名字,也可以直接传入自己写的正则表达式,如果不满意还可以传入自己写的替换方法,满足各种使用。
使用的例子:
1 2 3 4 5 6
| <input nz-input name="code" formControlName="code" id="code" appLimitInput [replace]="'number'" >
|
1
| regexp: RegExp = /[^\d|\.]/ig;
|
1 2 3 4 5 6
| <input nz-input name="code" formControlName="code" id="code" appLimitInput [replace]="regexp" >
|
1 2 3 4 5 6 7
| replaceFn = (el: ElementRef) => { console.log('replaceFn'); const regexp = /\d*\.?\d{0,8}/g; const match = el.nativeElement.value.match(regexp); el.nativeElement.value = match ? match[0] : ''; }
|
1 2 3 4 5 6
| <input nz-input name="code" formControlName="code" id="code" appLimitInput [replaceFn]="replaceFn" >
|
整理成指令后,修改成本降低了,代码也变得可扩展,业务复杂也不惧。