Angular 7 | HostListener 模糊条件停止所有其他事件回调

Angular 7 | HostListener blur conditional stop all other event callbacks

我有一个要求,我们希望用户解决输入字段错误,并且在完成之前不允许用户在屏幕上执行任何其他操作。

同样,我在输入字段和 onBlur 上实现了 HostListener,如果验证场景失败,我会将焦点设置回输入字段。

而且,我正在执行 e.preventDefault() 和 e.stopPropagtion() 以停止在页面上执行所有其他事件回调(在设置 focus 后,blur 将是第一个被执行的事件。

但是在任何外部事件上,blur 确实会执行,但它并不限制其他事件的执行。那些也正在执行,没有它们我将无法实现所需的功能。

import { Directive, HostListener, Input } from '@angular/core';

@Directive({
  selector: '[appNumberFormat]'
})
export class NumberFormatDirective {

  constructor() { }

  @HostListener('blur', ['$event']) blur(evt) {
    evt.preventDefault();
    console.log('field focus...');
    evt.target.focus();
    return false;
    // if (evt.target.value.trim() === '') {
    //     this.formControl.control.setValue(this.defaultValue);
    // }
  }
}

我在 stackBlitz 中复制了相同的场景。请看一下。

https://stackblitz.com/edit/angular-54ermg

您可以将额外的输入 属性 添加到 enable/disable 焦点功能。如果你有一个 Reactive 表单,你可以将它挂接到表单是 VALID 状态。

export class NumberFormatDirective {
  @Input() enableTargetFocus: boolean;

  @HostListener('blur', ['$event']) blur(evt) {
    if (this.enableTargetFocus === false) {
      return;
    }
    ...
  }
}

HTML:

<input type="text" appNumberFormat [enableTargetFocus]="form.invalid" />

解决此问题的一种方法是通过组件上的变量(指示表单是否有效)和阻止所有点击的简单 css 元素。

不过你必须要小心,确保你专注于正确的元素,而且这种情况不会永远持续下去,因为我不得不说从用户体验的角度来看这似乎相当烦人。

在您的 html 模板上:

<div *ngIf="formIsInvalid" class="click-blocker"></div>

在你的 css 上:

.click-blocker {
  position: absolute;
  background-color: rgba(1, 1, 1, 0.1); // adjust the opacity as you wish
  bottom: 0;
  left: 0;
  right: 0;
  top: 0;
  height: 100vh;
  width: 100vw;
  z-index: 1;
}

我在你的 stackblitz 中实现了它:https://stackblitz.com/edit/angular-za1lgx

我不太确定你想要实现什么,但一个技巧可能是根据你在 onBlur() 中被调用的不同情况限制你的 focus() 执行,因为你每次都会被调用由于您绑定了事件,因此要求模糊。

为了防止执行,您可以仅在需要时触发它,但这并不是很理想:假设您的 e.targete.relatedTarget 不是您想要的或类似的东西或当 document.activeElement 已经是输入井时,您不需要将其重新聚焦在 blur() 上,但又一次......不理想

希望对您有所帮助!

我认为这不是实现所需功能的理想方式。我认为其他 buttons/anchor-links 应该在表单中有错误时保持禁用状态。您可以将按钮和锚链接的禁用 属性 绑定到一个函数,该函数将在检查验证后 return true/false。

然而,浏览器按特定顺序执行事件,如果您需要停止点击事件的进行,您应该 return false。我已经使用一个布尔变量实现了相同的功能,该变量将根据验证检查设置为 true/false,我在这里创建了您的代码的分支 - https://stackblitz.com/edit/angular-uay8rm

  preventOtherActions: boolean = true;
  txtBoxValue: string = "";

  submitForm($event) {
    if(this.preventOtherActions) {
      return false;
    }
    console.log('submitForm');
  }

  onBlur(e) {
    console.log('field focus...');
    setTimeout(() => {
       e.target.focus();
      }, 10);
    this.preventOtherActions = this.txtBoxValue.length < 6;    
    return;
  }

Blur 不会阻止另一个事件,因为它是 运行 独立的。将指针事件设置为 none 将阻止点击。

onBlur(e) {
    e.preventDefault();
    e.stopPropagation();
    document.body.style.pointerEvents = 'none';
    console.log('field focus...');
    setTimeout(() => {
      e.target.focus();
    }, 10);

    setTimeout(() => {
      document.body.style.pointerEvents = 'initial';
    }, 300);

    return;
  }

您还可以在输入聚焦时添加叠加层,而不是使用 pointer-events: none。在这两种情况下,您都需要处理 Tab 键。

为了防止tab + enter在input元素上添加:

(keydown)="onKeyDown($event)" 

在组件 ts 中:

onKeyDown(event) {
    if (event.code === 'Tab') {
      event.preventDefault();
      event.stopPropagation();
    }
  }

您可以做的一种方法是使用此解决方案禁用页面上的所有事件(并自定义不允许输入键的按键)。

Stop propagation for all events

  disableAllUserEvents = () => {
    const events = ["click", "contextmenu", "dblclick", "mousedown", "mouseenter", "mouseleave", "mousemove",
        "mouseover", "mouseout", "mouseup", "blur", "change", "focus", "focusin",
        "focusout", "input", "invalid", "reset", "search", "select", "submit", "drag", "dragend", "dragenter",
        "dragleave", "dragover", "dragstart", "drop", "copy", "cut", "paste", "mousewheel", "wheel", "touchcancel",
        "touchend", "touchmove", "touchstart"];

    const handler = event => {
      event.stopPropagation();
      event.preventDefault();

      return false;
  };

    for (let i = 0, l = events.length; i < l; i++) {
        document.addEventListener(events[i], handler, true);
    }

    return () => {
        for (let i = 0, l = events.length; i < l; i++) {
            document.removeEventListener(events[i], handler, true);
        }
    };
  };

并在一切正常时重新启用该事件。这有点蛮力,但确实有效。

https://stackblitz.com/edit/angular-68vdrq