1.需求来源
最近负责的一个项目有做三级等保的需求,其中一个需求就是防止注入(即所有的input禁止输入特殊字符 ) ,后台老贼马上丢给我一个正则表达式:
[`~!@#$%^*()+|{}[].<>/?!()【】‘;:”“’。,、\]
让我在该项目的所有input上都加入这个过滤,该项目使用的是angular + ngzorro。搜索nz-input,会发现有的38个文件,好几百处。
我们目前采用的方案是增加了一个全局指令FilterSpecialCharDirective, 在需要过滤的input控件上添加该指令就可以。
2.show code
全局指令FilterSpecialCharDirective
import { Directive } from '@angular/core';
import { NgControl } from '@angular/forms';
import { specialStringRegexp } from '@data/const-val';
import { Subscription } from 'rxjs';
@Directive({
selector: '[filterSpecialChar]'
})
export class FilterSpecialCharDirective {
private subscription: Subscription;
constructor(private ngControl: NgControl) { }
ngOnInit() {
// 注意:在表单配置了updateOn: 'blur'的情况下,该订阅不会实时触发,是在 blur以后再触发的
const ctrl = this.ngControl.control;
if(!ctrl) { return; }
this.subscription = ctrl.valueChanges
.subscribe(v => {
if(!v) { return; }
ctrl.setValue(v.replace(specialStringRegexp, ''), { emitEvent: false });
});
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
在html使用的时候
<nz-form-item>
<nz-form-label nzSpan="8">姓名</nz-form-label>
<nz-form-control nzSpan="16">
<nz-input-group>
<input formControlName="name" nz-input placeholder="请输入姓名" filterSpecialChar>
</nz-input-group>
</nz-form-control>
</nz-form-item>
3.code 以外
3.1 如何在指令里面去更新ngmodel
这种指令过滤特殊字符的方式,比较亮眼的一点:就是在指令里面去更新model。一般我们都是在业务组件里面去更新model,即使有模板变量,也无法完美应对这种大范围处理的情况。
这篇文章How to update ngModel from directive? 里面提到了使用NgControl 来在指令里面去更新ngModel。
那么NgContorl是什么呢?在官网文档里面可以看到它是NgModel的的基类,其实属于比较底层的东西了。这里了解一下大概就可以
顺便看一下源码:
3.2 注意点
3.3.1 并非全部
并不是所有的input都需要该指令:
比如邮箱这种,就不能使用了,因为邮箱里面的@是必须的,而该指令的正则表达式式会把@当做特殊字符处理掉。
还有一些比如手机号和密码这种,一般都有自己的专属正则表达式,也不用该指令
3.3.2 更新时机
细心的jym,会发现在code里面有一句注释:
该指令默认的效果是你无法输入 正则里面禁止的那些特殊字符,就是输入以后无法在input输入框没有该字符,同时数据源上也不会有该字符。
但是在一些表单的input控件上你会发现,是可以输入特殊字符的,那是不是指令有bug呢?
非也,这是因为该表单配置了updateOn: 'blur' ,即控件是在失去焦点的时候才更新值,在失去焦点的时候才会触发valueChanges的变更
3.3 课外阅读
在搜素资料的时候,我看到一篇介绍指令的文章,其中在文章末尾,有个例子就是 自定义指令会去掉input输入框中的所有空格,实现也是在指令内部通过NgControl 来实现
3.4 抛砖引玉
虽然这种使用指令的方式,比oninput那种方式(这种方式在下面会说到)要更加便捷。但是依然需要手动在input代码处去一个个添加该指令。大家有什么更省事的方法,可以畅所欲言。
4. 弯路回顾
4.1 原始方案
其实在一开始,采用是
<input formControlName="name" nz-input oninput='value=value.replace(/正则/g, "")'>
这种原始的方式,但是这样写很不优雅、很不讲究,也不利于维护,jym想想你要在几百个代码写上这行的代码的感觉。再想想要是哪天又追加了一个特殊字符,你批量全局替换的囧态
4.2 好心办坏事
如果我就是一个不讲究的人,那么直接使用原始方案就完事了。但是就是可能太闲了,所以想着在原始方案的基础上优化优化: 首先就是得把正则做一个统一变量管理吧,以后利于维护,但是oninput是原生事件,不能在里面使用变量,所以我干脆新建了一个服务,然后搭配input事件来处理
① 统一管理变量的文件
export const specialStringRegexp = /[`~!@#$%^*()+|{}\[\].<>/?!()【】‘;:”“’。,、\\]/g;
② services文件:
import { Injectable } from '@angular/core';
import { specialStringRegexp } from '@data/const-val';
@Injectable({
providedIn: 'root'
})
export class FilterSpecialStringService {
constructor() { }
// 过滤特殊字符
filter($event) {
const value = $event.target.value;
$event.target.value = value.replace(specialStringRegexp, '');
}
}
③ 业务html文件:
<input formControlName="name" nz-input placeholder="请输入姓名" (input)="filterSpecialStrSrv.filter($event)">
④ 与html文件相关联的ts文件:
constructor(
public filterSpecialStrSrv: FilterSpecialStringService,
) {
}
聪明的jym马上就发现问题了,那就是修改$event.target.value 会造成视图和数据源的不一致,简单来说呢就是,输入特殊字符的时候输入框里面是没有值的,但是表单里面其实已经有了特殊字符的值。
4.3 它山之石可以攻玉
这种通用类的问题的解决,其实不用局限于框架,jym可以看看下面这篇文章 vue中的input使用e.target.value赋值的问题
另外可以看看这篇vue指令实战:过滤element input输入框中的特殊符号。