Angular2 + 自动对焦输入元件

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

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

Angular2是否支持这个特性?

最好的关闭问题是 这个,但是有没有更短/更容易的解决方案,因为我没有“输入框列表”。在提供的链接 *ngFor="#input of inputs"是使用,但我只有1个输入在控制模板。

101047 次浏览

您可以为输入元素分配一个模板引用变量 #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();
}
}

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

这是我现在的代码:

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它不工作。代码也使用最佳实践,因为直接调用元素的焦点不是 建议

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

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;
}
}

编辑2:
不推荐使用 Renderer.invkeElementMethod ,并且 new 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

下面的指令适用于我使用 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 生命周期事件的选项对我来说不起作用。我也尝试使用渲染器在另一个答案不适合我。

<input type="text" [(ngModel)]="title" #myInput />
\{\{ myInput.focus() }}

只需在模板输入后立即添加 \{\{ myInput.focus ()}即可

如果你不需要真/假功能,但想要自动对焦设置总是,有一个较短的实现 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 会使光标位于输入字段中的内容之后,以防它被填充。

请记住在您的模块 声明出口自动对焦指令!

从 IE11开始,和所有其他现代浏览器一样,原生的 HTML 自动对焦属性输入也应该能够很好地工作,而不需要依赖于 Angular:

<input autofocus>
 

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

更新

正如@adrug 在下面的评论中提到的,这种方法有一个缺陷。它只在页面加载时工作一次。

正如在 Netanel Basal 的文章中所解释的,一个更好的方法是将它作为一个角度指令来实现。正如其他答案所暗示的那样。

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

function addFocusInput() {
document.getElementById('text-focus').focus();
}


addFocusInput();
<input id="text-focus" type="text" placeholder=""/>

我知道这是一篇老文章,但对于其他寻找新答案的人来说: 我正在使用一个有角度的材料对话框,它是自动选择关闭按钮,而不是输入。

使用 cdk-focus-start(CDK 的一部分)修复了这个问题... 没有额外的代码。

我的解决办法是:

 <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();
}

焦点在角度生命周期中不能很好地工作。为了在场上强迫它,我运行一个观测器,发出每个“间隔”。如果已经呈现了元素,我可以通过它的 id 找到它。之后,我可以调整焦点。 如果找到 nbRetry > maxNbRetry 或者元素 id,我将使用 takTill 操作符停止可观察的内容。

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

以下是我对角度自动对焦指令的看法。

下面的指令接受布尔值作为输入,并根据已计算的布尔表达式对元素进行聚焦,以便可以动态地对元素进行聚焦。

此外,该指令可以直接应用于 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>

通知

请注意,这段代码只能在现代的浏览器环境中完全工作,但是,它不应该抛弃其他环境(优雅的降级)。

 myReactiveForm!: FormGroup;


constructor(public el: ElementRef) { }


onSubmit(myReactiveForm: any) {
this.autoFocusOnError(myReactiveForm, this.el);
}

使用此函数进行自动对焦:-

autoFocusOnError(form: any, el:any) {
form.markAllAsTouched();
for (const key of Object?.keys(form?.controls)) {
if (form?.controls[key].invalid) {
let rootElement = el.nativeElement;
let invalidControl = rootElement.querySelector('[formcontrolname="' + key + '"]') ||
rootElement.querySelector('[name="' + key + '"]');
    

if (invalidControl?.tagName == "NG-SELECT") {
invalidControl = invalidControl.querySelector("div.ng-select-container input");
} else if (invalidControl?.tagName == "NG-MULTISELECT-DROPDOWN") {
invalidControl = invalidControl.querySelector("div.multiselect-dropdown");
}
invalidControl?.focus();
return false;
}
}
return true;
}