【问题标题】:Angular2+ autofocus input elementAngular2+自动对焦输入元素
【发布时间】:2017-06-11 22:47:42
【问题描述】:

如何自动对焦输入元素?类似于this 问题,但不是AngularDart。像这样的:

<input type="text" [(ngModel)]="title" [focus] />
//or 
<input type="text" [(ngModel)]="title" autofocus />

Angular2 是否有任何内置支持此功能?

最好的结束问题是this one,但有没有更短/更简单的解决方案,因为我没有“输入框列表”。在提供的链接中使用了*ngFor="#input of inputs",但我在控件模板中只有 1 个输入。

【问题讨论】:

  • 第二种选择应该直接开箱即用。没有?
  • 简单地使用“自动对焦”是行不通的,因为它只在页面加载时有效,而不是在 Angular 交换内容时有效。
  • 绑定怎么样:[attr.autofocus]="condition"?

标签: angular typescript


【解决方案1】:

智能自动对焦(动态对焦)指令

这是我对 Angular 自动对焦指令的看法。

以下指令接受布尔值作为输入,并根据评估的布尔表达式聚焦元素,以便动态聚焦元素。

此外,该指令可以直接应用于input/button/select/a 元素,也可以应用于任何父元素。它将在 DOM 中搜索第一个合适的元素以自动聚焦。

代码

import { AfterViewInit, Directive, ElementRef, Input, NgModule } from '@angular/core';


const focusableElements = [
  'input',
  'select',
  'button',
  'a',
];


@Directive({
  selector: '[autofocus]',
})
export class AutofocusDirective implements AfterViewInit {

  @Input()
  public set autofocus(shouldFocus: boolean) {
    this.shouldFocus = shouldFocus;
    this.checkFocus();
  }

  private shouldFocus = true;


  constructor(
    private readonly elementRef: ElementRef
  ) {
  }


  public ngAfterViewInit() {
    this.checkFocus();
  }


  private checkFocus() {

    if (!this.shouldFocus) {
      return;
    }

    const hostElement = (
      <HTMLElement>
      this.elementRef.nativeElement
    );

    if (!hostElement) {
      return;
    }

    if (focusableElements.includes(
      hostElement.tagName.toLowerCase())
    ) {
      hostElement.focus?.();

    } else if (hostElement?.querySelector) {

      for (const tagName of focusableElements) {
        const childElement = (
          <HTMLInputElement>
            hostElement.querySelector(tagName)
        );
        if (childElement) {
          childElement?.focus?.();
          break;
        }
      }

    }

  }

}


@NgModule({
  declarations: [
    AutofocusDirective,
  ],
  exports: [
    AutofocusDirective,
  ],
})
export class AutofocusModule {
}

使用示例

<!-- These are equivalent: -->
<input type="text" autofocus>
<input type="text" [autofocus]>
<input type="text" [autofocus]="true">

<!-- Conditional (dynamic) focusing: -->
<input type="text" [autofocus]="shouldBeFocused">
<input type="text" name="username" [autofocus]="focusedField === 'username'">

<!-- Using parent element: -->
<fieldset autofocus>
  <label>
    Username:
    <input type="text">
  </label>
</fieldset>

通知

请注意,此代码只能在现代浏览器环境中完全工作,但不应在其他环境中抛出(优雅降级)。

【讨论】:

    【解决方案2】:

    从 IE11 开始,与所有其他现代浏览器一样,用于输入的原生 HTML autofocus Attribute 也应该可以正常工作,而无需绑定 Angular:

    <input autofocus>
     
    <input type="text" [(ngModel)]="title" autofocus>
    

    【讨论】:

    • Netanel Basal 解释这种方法的缺陷
    • 这仅适用于页面导航(如果幸运的话)。
    【解决方案3】:

    如果您不需要真/假功能,但希望始终设置自动对焦,那么 Makla 的解决方案有一个更短的实现:

    autofocus.directive.ts:

    import { Directive, ElementRef, Input, OnInit } from '@angular/core';
    
    @Directive({
        selector: '[autofocus]'
    })
    
    export class AutofocusDirective implements AfterViewInit {
    
        constructor(private el: ElementRef) {
        }
    
        ngAfterViewInit() {
            // Otherwise Angular throws error: Expression has changed after it was checked.
            window.setTimeout(() => {
                this.el.nativeElement.focus();
            });
        }
    }
    

    用例:

    <input autofocus> //will focus
    

    使用 AfterViewInit 而不是 OnInit 会使光标放置在输入字段内的内容之后,如果它被填充的话。

    记得在你的模块中declareexport自动对焦指令!

    【讨论】:

      【解决方案4】:

      我的解决方案:

       <input type="text" id="searchInput">
      
      // put focus on element with id 'searchInput', try every 100ms and retry 30 times
      this.focus('searchInput',30,100);
      
      focus( idElement:string, maxNbRetries:number, intervalMs:number){
      
          let nbRetries = 0;
          let elt = null;
          const stop$ = new Subject<boolean>();
          const source = interval(intervalMs);
          const source$ = source.pipe(
            tap(item=>{
              elt = document.getElementById(idElement);
              nbRetries++;
              if(nbRetries>maxNbRetries){
                stop$.next(true);
                console.log(`unable to put the focus on the element !`)
              }
            }),
            filter(item=>elt !=null),
            map(item=>{
              elt.focus();
              stop$.next(true);
            }),
            takeUntil(stop$)
      
          ).subscribe();
        }
      

      焦点不适用于角度生命周期。为了在一个字段上强制它,我运行了一个可观察到的,它会发出每个“intervalMs”。如果元素已被渲染,我可以通过它的 id 找到它。之后,我可以设置焦点。 如果找到 nbRetries > maxNbRetries 或元素 id,我会使用 takeUntil 运算符停止 observable。

      【讨论】:

        【解决方案5】:

        我知道这是一篇旧帖子,但对于寻找更新答案的其他人来说: 我使用的是角度材质对话框,它会自动选择关闭按钮而不是输入。

        使用cdk-focus-start(CDK 的一部分)解决了这个问题......无需额外的代码。

        【讨论】:

          【解决方案6】:

          试试这个简单但有效的功能。

          function addFocusInput() {
            document.getElementById('text-focus').focus();
          }
          
          addFocusInput();
          &lt;input id="text-focus" type="text" placeholder=""/&gt;

          【讨论】:

            【解决方案7】:

            您可以为输入元素分配一个模板引用变量#myInput

            <input type="text" [(ngModel)]="title" #myInput />
            

            让您的组件实现AfterViewInit,使用ViewChild 注解获取输入元素的引用,并将您的元素聚焦在ngAfterViewInit 挂钩中:

            export class MyComponent implements AfterViewInit {
                @ViewChild("myInput") private _inputElement: ElementRef;
            
                [...]
            
                ngAfterViewInit(): void {
                    this._inputElement.nativeElement.focus();
                }
            }
            

            【讨论】:

            • 我宁愿看到基本的方法(模板中的属性),所以我不需要在每个有表单的页面中编写所有这些代码。
            • 无法读取未定义的属性“nativeElement” - 在控制台上出现错误
            • @HasmukhBaldaniya 您是否添加了模板引用变量?您确定,您正在访问_inputElement视图已初始化?
            • 我收到“无法读取未定义的属性‘焦点’。”这是由于我的模板引用变量链接到数据(模板中的#myInput="ngModel")。解决方案是为该字段创建第二个模板引用变量。
            • @HasmukhAhir 有同样的错误,这是因为 *ngIf。请参阅此stackoverflow.com/questions/39366981/viewchild-in-ngif + 在需要时使用 ChangeDetectorRef
            【解决方案8】:
            <input type="text" [(ngModel)]="title" #myInput />
            {{ myInput.focus() }}
            

            只需在模板内输入之后添加 {{ myInput.focus() }}

            【讨论】:

            • 干杯这是最好的
            • 我同意,对我来说是现在唯一的工作权限。但是,我不喜欢这种逻辑,我不明白为什么既不使用本机 html 自动对焦也不使用指令
            • 很棒的答案!
            • @Sergey Gurin 但在这种情况下,每次在 mouseOver 上都会调用它。这会在创建构建时产生错误吗?
            • 不是一个好的解决方案。这可能永远不会让控件松散焦点,因为 Angular 会在每次输入更改、鼠标悬停等时评估并应用焦点......
            【解决方案9】:

            这是我当前的代码:

            import { Directive, ElementRef, Input } from "@angular/core";
            
            @Directive({
                selector: "[autofocus]"
            })
            export class AutofocusDirective
            {
                private focus = true;
            
                constructor(private el: ElementRef)
                {
                }
            
                ngOnInit()
                {
                    if (this.focus)
                    {
                        //Otherwise Angular throws error: Expression has changed after it was checked.
                        window.setTimeout(() =>
                        {
                            this.el.nativeElement.focus(); //For SSR (server side rendering) this is not safe. Use: https://github.com/angular/angular/issues/15008#issuecomment-285141070)
                        });
                    }
                }
            
                @Input() set autofocus(condition: boolean)
                {
                    this.focus = condition !== false;
                }
            }
            

            用例:

            [autofocus] //will focus
            [autofocus]="true" //will focus
            [autofocus]="false" //will not focus
            

            过时的代码(旧答案,以防万一):
            我最终得到了这段代码:

            import {Directive, ElementRef, Renderer} from '@angular/core';
            
            @Directive({
                selector: '[autofocus]'
            })
            export class Autofocus
            {
                constructor(private el: ElementRef, private renderer: Renderer)
                {        
                }
            
                ngOnInit()
                {        
                }
            
                ngAfterViewInit()
                {
                    this.renderer.invokeElementMethod(this.el.nativeElement, 'focus', []);
                }
            }
            

            如果我将代码放入ngOnViewInit,它就不起作用。代码也使用了最佳实践,因为直接调用元素的焦点不是recommended

            已编辑(条件自动对焦):
            几天前我需要有条件的自动对焦,因为我隐藏了第一个自动对焦元素,我想对焦另一个,但只有当第一个不可见时,我以这段代码结束:

            import { Directive, ElementRef, Renderer, Input } from '@angular/core';
            
            @Directive({
                selector: '[autofocus]'
            })
            export class AutofocusDirective
            {
                private _autofocus;
                constructor(private el: ElementRef, private renderer: Renderer)
                {
                }
            
                ngOnInit()
                {
                }
            
                ngAfterViewInit()
                {
                    if (this._autofocus || typeof this._autofocus === "undefined")
                        this.renderer.invokeElementMethod(this.el.nativeElement, 'focus', []);
                }
            
                @Input() set autofocus(condition: boolean)
                {
                    this._autofocus = condition != false;
                }
            }
            

            Edited2:
            Renderer.invokeElementMethod is deprecated 和新的 Renderer2 不支持它。 所以我们回到原生焦点(这在 DOM 之外不起作用 - 例如 SSR!)。

            import { Directive, ElementRef, Input } from '@angular/core';
            
            @Directive({
                selector: '[autofocus]'
            })
            export class AutofocusDirective
            {
                private _autofocus;
                constructor(private el: ElementRef)
                {
                }
            
                ngOnInit()
                {
                    if (this._autofocus || typeof this._autofocus === "undefined")
                        this.el.nativeElement.focus();      //For SSR (server side rendering) this is not safe. Use: https://github.com/angular/angular/issues/15008#issuecomment-285141070)
                }
            
                @Input() set autofocus(condition: boolean)
                {
                    this._autofocus = condition != false;
                }
            }
            

            用例:

            [autofocus] //will focus
            [autofocus]="true" //will focus
            [autofocus]="false" //will not focus
            

            【讨论】:

            • 这也是我在阅读完这篇文章后的结尾...angularjs.blogspot.co.uk/2016/04/…
            • 我使用了这段代码,OnInit 为我工作。我不必使用AfterViewInit
            • @Rodrigo:我想确定一下。
            • 很好的答案!谢谢马克拉
            • 当我实现这个时,我得到了 'ExpressionChangedAfterItHasBeenCheckedError' 异常。我尝试在 .focus() 之后立即使用 detectChanges() 但它没有用。
            【解决方案10】:

            autofocus 是一个原生的 html 特性,至少应该可以用于页面初始化。然而,它无法处理许多角度场景,尤其是*ngIf

            您可以制作一个非常简单的自定义指令来获得所需的行为。

            import { Directive, OnInit, ElementRef } from '@angular/core';
            
            @Directive({
              selector: '[myAutofocus]'
            })
            export class AutofocusDirective implements OnInit {
            
              constructor(private elementRef: ElementRef) { };
            
              ngOnInit(): void {
                this.elementRef.nativeElement.focus();
              }
            
            }
            

            上述指令适用于我的用例。

            如何使用

            <input *ngIf="someCondition" myAutofocus />
            

            编辑:似乎有些用例在 OnInit 生命周期方法中调用焦点还为时过早。如果是这种情况,请改为 OnAfterViewInit

            【讨论】:

              【解决方案11】:

              以下指令适用于我使用 Angular 4.0.1

              import {Directive, ElementRef, AfterViewInit} from '@angular/core';
              
              @Directive({
                selector: '[myAutofocus]'
              })
              export class MyAutofocusDirective implements AfterViewInit {
                constructor(private el: ElementRef)
                {
                }
                ngAfterViewInit()
                {
                  this.el.nativeElement.focus();
                }
              }
              

              像这样使用它:

              <md-input-container>
                  <input mdInput placeholder="Item Id" formControlName="itemId" name="itemId" myAutofocus>
              </md-input-container>
              

              使用 OnInit 生命周期事件的选项对我不起作用。我还尝试在另一个对我不起作用的答案中使用渲染器。

              【讨论】:

                猜你喜欢
                • 2019-06-27
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2013-06-20
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多