【问题标题】:Angular 7 | HostListener blur conditional stop all other event callbacks角 7 | HostListener blur 有条件地停止所有其他事件回调
【发布时间】:2019-09-19 13:00:34
【问题描述】:

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

同样,我在输入字段和 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

【问题讨论】:

  • @Liam 他实际上在 stackblitz 中提供了代码,我只是将其添加到问题中以避免关闭。
  • 请问您为什么不使用(模糊)事件绑定?是否要求它必须是指令?

标签: javascript angular onblur onfocus


【解决方案1】:

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

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+enter在input元素上添加:

(keydown)="onKeyDown($event)" 

在组件 ts 中:

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

【讨论】:

  • 此解决方案尝试在 onBlur 方法执行时阻止页面操作,但在焦点重新设置后立即启用所有事件。这并不限制正在执行的其他操作。
  • 你说得对,如果我足够努力的话,我已经设法偷偷点击了几下。我已经编辑了我的答案,以在更长的超时后将指针事件设置为初始值。看来 10 毫秒太短了。
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

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

    尽管如此,浏览器会以特定顺序执行事件,如果您需要阻止点击事件继续进行,则应返回 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;
      }
    

    【讨论】:

    • 维护变量以保持错误状态不是一个可行的解决方案,因为我在页面上有动态控件并且可以是 (n) 个字段。另外,页面上有多个链接(routerLink)、按钮和下拉菜单,你认为这可以在那里实现吗?我用 vanilla/jQuery 检查了相同的实现......这些它工作得很好。返回 false 时,其他事件停止。它只是 Angular 的东西,事件并没有停止。
    • @ManishKumar 我认为您正在尝试在单个 onBlur 函数中处理差异事物。相反,每个场景都应该单独处理。例如 1. routerLink 场景应该使用 Angular 的 canDeactivate 保护来处理 2. 将所有控件放在 Angular 表单中,如果表单无效,按钮/链接应该保持禁用状态 3. 即使有错误,也让用户点击其他任何地方并突出显示输入字段(例如,将有错误的边框设为红色。
    【解决方案4】:

    您可以添加一个额外的 Input 属性来启用/禁用焦点功能。如果你有一个 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" />
    

    【讨论】:

    • 该解决方案没有讨论限制其他页面操作执行的主要问题。
    【解决方案5】:

    解决此问题的一种方法是通过组件上的变量(指示表单是否有效)和阻止所有点击的简单 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

    【讨论】:

    • 我需要限制甚至是标题导航/左侧面板导航以及屏幕上任何位置可用的任何其他用户操作,而不仅仅是组件本身。而且,还必须完成此任务的 100 个字段。用于维护无效状态的单个变量在这里无济于事。
    【解决方案6】:

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

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

    希望这会有所帮助!

    【讨论】:

      猜你喜欢
      • 2021-02-27
      • 1970-01-01
      • 2018-10-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-05-09
      • 1970-01-01
      • 2014-03-10
      相关资源
      最近更新 更多