angular全局过滤特殊字符

789 阅读1分钟

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的的基类,其实属于比较底层的东西了。这里了解一下大概就可以

image.png

image.png

顺便看一下源码:

16b638f9f7ef87e6ae966264f50ae909.png

3.2 注意点

3.3.1 并非全部

并不是所有的input都需要该指令:

比如邮箱这种,就不能使用了,因为邮箱里面的@是必须的,而该指令的正则表达式式会把@当做特殊字符处理掉。

还有一些比如手机号和密码这种,一般都有自己的专属正则表达式,也不用该指令

3.3.2 更新时机

细心的jym,会发现在code里面有一句注释:

image.png

该指令默认的效果是你无法输入 正则里面禁止的那些特殊字符,就是输入以后无法在input输入框没有该字符,同时数据源上也不会有该字符。

但是在一些表单的input控件上你会发现,是可以输入特殊字符的,那是不是指令有bug呢?

非也,这是因为该表单配置了updateOn: 'blur' ,即控件是在失去焦点的时候才更新值,在失去焦点的时候才会触发valueChanges的变更

3.3 课外阅读

在搜素资料的时候,我看到一篇介绍指令的文章,其中在文章末尾,有个例子就是 自定义指令会去掉input输入框中的所有空格,实现也是在指令内部通过NgControl 来实现

1d7d770b51910336de2fc95c60231d94.png

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赋值的问题

image.png

另外可以看看这篇vue指令实战:过滤element input输入框中的特殊符号