【问题标题】:Set focus on <input> element将焦点设置在 <input> 元素上
【发布时间】:2018-10-05 00:32:17
【问题描述】:

我正在使用 Angular 5 开发一个前端应用程序,我需要隐藏一个搜索框,但是单击按钮后,搜索框应该会显示出来并获得焦点。

我已经尝试了一些在 StackOverflow 上找到的带有指令的方法,但都无法成功。

这里是示例代码:

@Component({
   selector: 'my-app',
   template: `
    <div>
    <h2>Hello</h2>
    </div>
    <button (click) ="showSearch()">Show Search</button>
    <p></p>
    <form>
      <div >
        <input *ngIf="show" #search type="text"  />            
      </div>
    </form>
    `,
  })
  export class App implements AfterViewInit {
  @ViewChild('search') searchElement: ElementRef;

  show: false;
  name:string;
  constructor() {    
  }

  showSearch(){
    this.show = !this.show;    
    this.searchElement.nativeElement.focus();
    alert("focus");
  }

  ngAfterViewInit() {
    this.firstNameElement.nativeElement.focus();
  }

搜索框未设置为焦点。

我该怎么做?

【问题讨论】:

    标签: angular angular5 angular-forms


    【解决方案1】:

    像这样修改节目搜索方法

    showSearch(){
      this.show = !this.show;  
      setTimeout(()=>{ // this will make the execution after the above boolean has changed
        this.searchElement.nativeElement.focus();
      },0);  
    }
    

    【讨论】:

    • 为什么我们需要使用setTimeout?布尔值的变化不是同步的吗?
    • 这不是和 zonejs 搞混了吗?
    • @AndreiRosu,没有它我会收到一个错误,因为没有呈现更改
    • 这行得通,但没有明确的解释,它很神奇。魔术经常中断。
    • tl;dr:使用 setTimeout 使您的代码异步,通过将函数执行添加到事件循环,并在执行时第二次触发更改检测。它确实会影响性能。
    【解决方案2】:

    您应该为此使用 HTML autofocus

    <input *ngIf="show" #search type="text" autofocus /> 
    

    注意:如果你的组件被持久化和重用,它只会在第一次附加片段时自动聚焦。这可以通过使用global DOM listener 来解决,该global DOM listener 在附加时检查 DOM 片段内的 autofocus 属性,然后重新应用它或通过 JavaScript 聚焦。

    这是一个全局监听器示例,它只需要在您的 spa 应用程序中放置一次,无论同一个片段被重复使用多少次,自动对焦都会起作用:

    (new MutationObserver(function (mutations, observer) {
        for (let i = 0; i < mutations.length; i++) {
            const m = mutations[i];
            if (m.type == 'childList') {
                for (let k = 0; k < m.addedNodes.length; k++) {
                    const autofocuses = m.addedNodes[k].querySelectorAll("[autofocus]"); //Note: this ignores the fragment's root element
                    console.log(autofocuses);
                    if (autofocuses.length) {
                        const a = autofocuses[autofocuses.length - 1]; // focus last autofocus element
                        a.focus();
                        a.select();
                    }
                }
            }
        }
    })).observe(document.body, { attributes: false, childList: true, subtree: true });
    

    【讨论】:

    • 这只会在每次页面刷新时起作用,而不是多次。
    【解决方案3】:

    该指令将在元素显示后立即聚焦并选择元素中的任何文本。在某些情况下,这可能需要一个 setTimeout,它尚未经过太多测试。

    import { Directive, ElementRef, OnInit } from '@angular/core';
        
    @Directive({
      selector: '[appPrefixFocusAndSelect]',
    })
    export class FocusOnShowDirective implements OnInit {    
      constructor(private el: ElementRef) {
        if (!el.nativeElement['focus']) {
          throw new Error('Element does not accept focus.');
        }
      }
        
      ngOnInit(): void {
        const input: HTMLInputElement = this.el.nativeElement as HTMLInputElement;
        input.focus();
        input.select();
      }
    }
    

    在 HTML 中:

    <mat-form-field>
      <input matInput type="text" appPrefixFocusAndSelect [value]="'etc'">
    </mat-form-field>
    

    【讨论】:

    • 谢谢,在等待http请求的情况下,这是唯一对我有用的解决方案
    • 我发现这个解决方案比接受的答案更可取。在代码重用的情况下它更简洁,并且更以 Angular 为中心。
    • 另外不要忘记在模块的声明中添加指令。
    • Similar answer 使用更简单的 Renderer2 替代方案。
    • 该解决方案适用于 Angular 4,只需要使用 setTimeout 函数包装焦点和选择的设置。谢谢!
    【解决方案4】:

    我将对此进行权衡(Angular 7 解决方案)

    input [appFocus]="focus"....
    
    import {AfterViewInit, Directive, ElementRef, Input,} from '@angular/core';
    
    @Directive({
      selector: 'input[appFocus]',
    })
    export class FocusDirective implements AfterViewInit {
    
      @Input('appFocus')
      private focused: boolean = false;
    
      constructor(public element: ElementRef<HTMLElement>) {
      }
    
      ngAfterViewInit(): void {
        // ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked.
        if (this.focused) {
          setTimeout(() => this.element.nativeElement.focus(), 0);
        }
      }
    }
    

    【讨论】:

    • 这似乎与accepted answer非常相似
    • 最佳解决方案,但重点不能是私人的
    【解决方案5】:

    这在没有 setTimeout 的 Angular 8 中工作:

    import {AfterContentChecked, Directive, ElementRef} from '@angular/core';
    
    @Directive({
      selector: 'input[inputAutoFocus]'
    })
    export class InputFocusDirective implements AfterContentChecked {
      constructor(private element: ElementRef<HTMLInputElement>) {}
    
      ngAfterContentChecked(): void {
        this.element.nativeElement.focus();
      }
    }
    

    说明: 好的,这是因为:更改检测。这与 setTimout 工作的原因相同,但是当在 Angular 中运行 setTimeout 时,它将绕过 Zone.js 并再次运行所有检查,并且它可以工作,因为当 setTimeout 完成时,所有更改都已完成。使用正确的生命周期挂钩 (AfterContentChecked) 可以达到相同的结果,但优点是不会运行额外的循环。当检查并通过所有更改时,该函数将触发,并在挂钩 AfterContentInit 和 DoCheck 之后运行。如果我在这里错了,请纠正我。

    https://angular.io/guide/lifecycle-hooks上的多个生命周期和变更检测

    更新: 如果使用 Angular Material CDK(a11y 包),我发现了一种更好的方法。 首先在声明您拥有输入字段的组件的模块中导入 A11yModule。 然后使用 cdkTrapFocuscdkTrapFocusAutoCapture 指令并在 html 中像这样使用并在输入上设置 tabIndex:

    <div class="dropdown" cdkTrapFocus cdkTrapFocusAutoCapture>
        <input type="text tabIndex="0">
    </div>
    

    我们的下拉菜单在定位和响应方面存在一些问题,因此开始使用 cdk 中的 OverlayModule,而这种使用 A11yModule 的方法完美无缺。

    【讨论】:

    • 嗨...我不尝试cdkTrapFocusAutoCapture 属性,但我将您的第一个示例更改为我的代码。由于未知的原因(对我而言),它不适用于 AfterContentChecked 生命周期钩子,而仅适用于 OnInit。特别是 AfterContentChecked,如果没有相同形式的指令,我无法更改焦点并(使用鼠标或键盘)移动到另一个输入。
    • @timhecker 是的,我们在将组件迁移到 cdk 覆盖层时遇到了类似的问题(它在仅使用 css 而不是覆盖层进行定位时起作用)。当使用该指令的组件可见时,它会无限期地关注输入。我找到的唯一解决方案是使用更新中提到的 cdk 指令。
    【解决方案6】:

    在 Angular 中,在 HTML 本身中,您可以将焦点设置为单击按钮时的输入。

    <button (click)="myInput.focus()">Click Me</button>
    
    <input #myInput></input>
    

    【讨论】:

    • 这个答案显示了一种以编程方式在 HTML 中选择另一个元素的简单方法,这正是我正在寻找的(所有“初始焦点”答案都没有解决如何通过改变焦点来对事件做出反应) ) - 不幸的是,我需要对在其他 UI 内容之前发生的 mat-select selectionChanged 事件做出反应,因此此时拉焦点不起作用。相反,我必须编写一个方法setFocus(el:HTMLElement):void { setTimeout(()=&gt;el.focus(),0); } 并从事件处理程序中调用它:&lt;mat-select (selectionChanged)="setFocus(myInput)"&gt;。不是很好,但很简单,效果很好。谢谢!
    • 对我不起作用。我得到 ERROR TypeError: Cannot read property 'focus' of undefined
    • @RezaTaba,你是调用函数 'myInput.focus()' 还是只写了 'myInput.focus' ?另外,请确保您的输入元素位于呈现的 div 内(例如,*ngIf false 可能会导致错误)
    • Sandipan Mitra 的相同答案
    【解决方案7】:

    要在布尔值更改后执行并避免使用超时,您可以这样做:

    import { ChangeDetectorRef } from '@angular/core';
    
    constructor(private cd: ChangeDetectorRef) {}
    
    showSearch(){
      this.show = !this.show;  
      this.cd.detectChanges();
      this.searchElement.nativeElement.focus();
    }
    

    【讨论】:

    • 我用 Angular 7 试过这个,它没有用,使用超时工作正常
    • 对我来说,角度 8 也不起作用,糟糕的是我们必须回到 setTimeout
    • 工作正常,由于setTimeout 影响页面的完全刷新,设置而不是 setTimeout。
    【解决方案8】:

    组件的html:

    <input [cdkTrapFocusAutoCapture]="show" [cdkTrapFocus]="show">
    

    组件的控制器:

    showSearch() {
      this.show = !this.show;    
    }
    

    ..不要忘记从@angular/cdk/a11y导入A11yModule

    import { A11yModule } from '@angular/cdk/a11y'
    

    【讨论】:

    • 最佳解决方案。谢谢。其他解决方案给了我错误。
    • 很好的解决方案 - 效果很好并且无痛。
    【解决方案9】:

    还有一个名为 cdkFocusInitial 的 DOM 属性在输入上对我有用。 你可以在这里阅读更多信息:https://material.angular.io/cdk/a11y/overview

    【讨论】:

      【解决方案10】:

      我有同样的情况,这对我有用,但我没有你拥有的“隐藏/显示”功能。因此,也许您可​​以首先检查当您始终可见该字段时是否获得焦点,然后尝试解决为什么在您更改可见性时不起作用(可能这就是您需要应用睡眠或承诺的原因)

      要设置焦点,这是您需要做的唯一更改:

      您的 Html mat 输入应该是:

      <input #yourControlName matInput>
      

      在您的 TS 类中,在变量部分(

      export class blabla...
          @ViewChild("yourControlName") yourControl : ElementRef;
      

      你的按钮没问题,正在调用:

        showSearch(){
             ///blabla... then finally:
             this.yourControl.nativeElement.focus();
      }
      

      就是这样。 您可以在我找到的这篇文章中查看此解决方案,感谢 --> https://codeburst.io/focusing-on-form-elements-the-angular-way-e9a78725c04f

      【讨论】:

      • 这适用于我的用例:如果用户在菜单中选择了特定选项,我需要显示一个输入框,并且我希望它仅在该场景中实现焦点(不是自动,之后组件已初始化,这就是 autofocus 所做的)。
      【解决方案11】:

      仅使用 Angular 模板

      <input type="text" #searchText>
      
      <span (click)="searchText.focus()">clear</span>
      

      【讨论】:

      • "如果你使用的是 Material..."
      • ERROR TypeError: Cannot read property 'focus' of undefined
      • 这仅在输入元素来自 b4 click 元素时才有效,这不是我的情况,它不起作用Property 'searchText' does not exist on type...
      【解决方案12】:

      更简单的方法也是这样做。

      let elementReference = document.querySelector('<your css, #id selector>');
          if (elementReference instanceof HTMLElement) {
              elementReference.focus();
          }
      

      【讨论】:

      • 你真的不想直接查询dom。
      猜你喜欢
      • 2020-04-30
      • 2011-01-24
      • 2021-02-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-06-18
      • 2020-05-15
      • 1970-01-01
      相关资源
      最近更新 更多