Angular指令监听touch事件无法捕获markAllAsTouched触发问题
问题原因
你当前实现的指令仅绑定了input、keydown两个原生DOM事件来触发样式更新逻辑,但this.form.markAllAsTouched()是Angular表单层面的程序式状态调用,不会触发任何DOM事件,因此样式切换逻辑根本不会执行,最终导致样式不生效。
修复方案
不要仅依赖DOM事件触发样式更新,需要同时覆盖程序调用触发的表单状态变更场景,修复后的完整指令代码如下:
import { Directive, ElementRef, HostListener, OnInit, OnDestroy } from '@angular/core'; import { NgControl } from '@angular/forms'; import { Subject, takeUntil } from 'rxjs'; @Directive({ selector: '[validatorClass]' }) export class ValidatorClassDirective implements OnInit, OnDestroy { private destroy$ = new Subject<void>(); // 缓存上一次的控件状态,避免重复操作DOM private lastState = { touched: false, hasError: false, hasValue: false }; constructor( private el: ElementRef<HTMLElement>, private control: NgControl ) { } ngOnInit(): void { const control = this.control.control; if (!control) return; // 订阅值变化、校验状态变化事件 control.valueChanges .pipe(takeUntil(this.destroy$)) .subscribe(() => this.setClass()); control.statusChanges .pipe(takeUntil(this.destroy$)) .subscribe(() => this.setClass()); // 代理原生markAsTouched方法,捕获程序式触发的touched状态变更 const originalMarkAsTouched = control.markAsTouched.bind(control); control.markAsTouched = (opts?) => { originalMarkAsTouched(opts); this.setClass(); }; // 组件初始化时执行一次样式判断,覆盖初始状态场景 this.setClass(); } // 补充blur事件监听,符合常规表单失焦校验的交互逻辑 @HostListener('blur') @HostListener('input') @HostListener('keydown') setClass(): void { const control = this.control.control; if (!control) return; const currentTouched = control.touched; const currentHasError = !!control.errors; const currentHasValue = !!control.value?.toString().trim(); // 状态无变化时跳过DOM操作,减少性能损耗 if ( this.lastState.touched === currentTouched && this.lastState.hasError === currentHasError && this.lastState.hasValue === currentHasValue ) { return; } // 先清空所有状态样式 this.el.nativeElement.classList.remove('border-danger', 'border-success'); if (currentTouched) { if (currentHasError) { this.el.nativeElement.classList.add('border-danger'); } else if (currentHasValue) { this.el.nativeElement.classList.add('border-success'); } } // 更新缓存状态 this.lastState = { touched: currentTouched, hasError: currentHasError, hasValue: currentHasValue }; } ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); } }
修复点说明
- 新增
blur事件监听,用户输入完成失焦时即可触发样式校验,符合通用表单交互习惯 - 订阅表单控件自带的
valueChanges、statusChanges流,覆盖值变更、校验状态变更场景 - 代理控件原生的
markAsTouched方法,不管是用户交互触发的touched状态变更,还是程序调用markAsTouched()、markAllAsTouched()(markAllAsTouched内部会遍历所有子控件调用markAsTouched),都能正常触发样式更新 - 增加状态缓存逻辑,仅在控件状态实际发生变化时才操作DOM,避免不必要的性能损耗
- 增加初始化执行逻辑,页面首次加载、控件初始带状态的场景下也能正确显示样式
- 增加指令销毁时的取消订阅逻辑,避免内存泄漏
内容的提问来源于stack exchange,提问作者mandrilla34
相关产品推荐
相关产品推荐

