Angular2+ autofocus input element
Asked Answered
M

13

80

How can I autofocus input element? Similar to this question, but not with AngularDart. Something like this:

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

Does Angular2 has any build in support for this feature?

Best close question is this one, but is there any shorter/easier solution, since I do not have "list of input boxes". In provided link *ngFor="#input of inputs" is used, but I only have 1 input in control template.

Mesopotamia answered 26/1, 2017 at 12:53 Comment(3)
second alternative should work straight out of the box. It doesn't?Lollard
Simply using "autofocus" doesn't work because that is only effective when the page loads, not when Angular is swapping content in and out.Sorcim
What about binding: [attr.autofocus]="condition"?Dulosis
M
102

This is my current code:

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

use case:

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

Outdated code (old answer, just in case):
I ended up with this code:

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', []);
    }
}

If I put code in ngOnViewInit it does not work. Code also use best practices, since calling focus on element directly is not recommended.

Edited (conditional autofocus):
A few days ago I needed conditional auto focus, because I hide first autofocus element and I want to focus another, but only when first is not visible, and I ended with this code:

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 and new Renderer2 does not support it. So we are back to native focus (which doesn't work outside DOM - SSR for example!).

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

use case:

[autofocus] //will focus
[autofocus]="true" //will focus
[autofocus]="false" //will not focus
Mesopotamia answered 12/3, 2017 at 8:34 Comment(10)
This is what I ended with too, after reading through this ... angularjs.blogspot.co.uk/2016/04/…Mccarty
I used this code, and the OnInit worked for me. I didn't have to use the AfterViewInit.Earlineearls
@Rodrigo: I wanted to be sure.Mesopotamia
Doesn't work for me, using latest Angular(5) and Ionic(3) loads fine, tried both version (ngOnInit and ngAfterViewInit). Not sure what I'm missing but it looks like the code is running but the view isn't there yetNogging
@GuyNesher Hi, I am using Angular 5.0.5 and before that 5.0.0 and it works in both cases. Hope this helps you solved the problem.Mesopotamia
When I implemented this, I got 'ExpressionChangedAfterItHasBeenCheckedError' exception. I tried to use detectChanges() right after .focus() but it didn't work.Saw
@Saw I had the same problem (not sure with which Angular error starts showing) and I didn't figure it out why. I also ask question on SO, but I can not find thread right now. I update my answer. It has my latest code which works. I do not like solution with setTimeout but that was the only way I manage to get it work. Hope it will works for you. Let me know. My current Angular version is 5.2.3.Mesopotamia
@Mesopotamia Thank you it works for me. But I don't like those kind of patches. Hopefully someone will find a solution for this someday :(Saw
Me neither (like I sad), but I tried many things, search Google/SO. I didn't find solution. Maybe Günter Zöchbauer can share some light on this problem.Mesopotamia
@Mesopotamia when I use the edited2 code snippet, the compiler gives me the following Error: Member '_autofocus' implicitly has an 'any' type.Fishmonger
L
50

autofocus is a native html feature that should work at least for page initialization. However it fails to work with many angular scenarios, especially with *ngIf.

You can make a really simple custom directive to get desired behaviour.

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

}

The above directive works for my use cases.

How to use

<input *ngIf="someCondition" myAutofocus />

EDIT: There seems to be usecases where it is to early to call focus in the OnInit lifecycle method. If that is the case, change to OnAfterViewInit instead.

Lollard answered 30/1, 2017 at 13:48 Comment(0)
G
25
<input type="text" [(ngModel)]="title" #myInput />
{{ myInput.focus() }}

just add {{ myInput.focus() }} right after input inside template

Gloomy answered 17/5, 2018 at 8:59 Comment(6)
Cheers this is the best oneAretino
I agree, to me is the only one working rigth now. However, I don't like the logic and I don't understand why is not working with the native html autofocus neither with the directiveCully
awesome answer!Cardiovascular
@Sergey Gurin but in this case, this is called every time on mouseOver. Can this create an error while creating a build?Sheaves
I don't think it can affect to buildGloomy
Not a good solution. This will probably never let the control loose focus as angular will evaluate and apply focus with each input change, mouse over, etc...Cadmann
Z
23

You could assign the input element a template reference variable #myInput:

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

Let your component implement AfterViewInit, grab the reference of the input element with the ViewChild annotation and focus your element in the ngAfterViewInit hook:

export class MyComponent implements AfterViewInit {
    @ViewChild("myInput") private _inputElement: ElementRef;

    [...]

    ngAfterViewInit(): void {
        this._inputElement.nativeElement.focus();
    }
}
Zygoma answered 26/1, 2017 at 13:3 Comment(6)
I would rather see basic approach (attribute in template), so I don't need to write all this code in every page that has form.Mesopotamia
Cannot read property 'nativeElement' of undefined - Getting error on consoleKaryotype
@HasmukhBaldaniya Did you add the template reference variable? And are you sure, you're accessing _inputElement after the view has been initialized?Zygoma
I was getting "Cannot read property 'focus' of undefined." It was due to my template reference variable being linked to data (#myInput="ngModel" in the template). The solution was to make a second template reference variable for that field.Thomasson
@HasmukhAhir Had this same error and it was because of the *ngIf. See this https://mcmap.net/q/86241/-viewchild-in-ngif + use ChangeDetectorRefwhen neededPantomime
I have used this solution in a Angualr Material / BottomSheet (I guess the same apply for dialogs) and to make it work I had to wrap it with a setTimeout().Kmeson
G
22

The following directive works for me using 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();
  }
}

use it like this:

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

The option of using OnInit lifecycle event did not work for me. I also tried using the Renderer in the other answer which didn't work for me.

Giuliana answered 12/5, 2017 at 17:49 Comment(0)
E
14

If you don't need true/false functionality, but want autofocus set always, there's a shorter implementation to Makla's solution:

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

use case:

<input autofocus> //will focus

Using AfterViewInit instead of OnInit makes the cursor placed after the content inside input field, should it get populated.

Remember to declare and export autofocus directive in your module!

Ens answered 5/3, 2019 at 15:10 Comment(0)
G
4

Starting from IE11, as all other modern browsers, the native HTML autofocus Attribute for inputs should work fine too without tying on Angular:

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

UPDATE

As mentioned by @adrug's comment below, this approach has a pitfall. It will only work once, when the page is loaded.

As explained in Netanel Basal's article, a better way is to implement it as an Angular Directive. As it was already suggested by other answers.

Genital answered 12/2, 2020 at 13:31 Comment(2)
Netanel Basal explain pitfall this approachUlm
This will work only on page navigation (if you are lucky).Tenorio
T
3

Smart autofocus (dynamic focus) directive

Here's my take on Angular autofocus directive.

The following directive accepts boolean value as an input and focuses the element based on the evaluated boolean expression so the element could be focused dynamically.

Also, the directive could be applied to the input/button/select/a elements directly or to any parent element as well. It will search the DOM for the first suitable element to focus automatically.

The code

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

Usage examples

<!-- 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>

Notice

Be advised, that this code will fully work only in modern browser environment, however, it shouldn't throw in other environments (graceful degradation).

Tenorio answered 5/11, 2021 at 21:56 Comment(0)
F
1

Try this simple but effective function.:

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

addFocusInput();
<input id="text-focus" type="text" placeholder=""/>
Flunky answered 14/5, 2020 at 17:24 Comment(1)
You shouldn't use getElementById in Angular, use ViewChild insteadVicegerent
G
1

I know this is an old post but for others looking for a newer answer: I was using an angular material dialog and it was auto-selecting the close button not the input.

Using cdk-focus-start (part of the CDK) fixes this ... with no additional code.

Goshen answered 11/6, 2020 at 14:23 Comment(0)
P
0

My solution :

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

Focus does not work well with angular lifecycle. To force it on a field, i run an observable that emits every 'intervalMs'. If the element has been rendered, i can find it by its id. After that, i can set the focus. If nbRetries > maxNbRetries or element id is found, i stop the observable with the takeUntil operator.

Papoose answered 25/8, 2020 at 13:30 Comment(0)
D
0
 myReactiveForm!: FormGroup;

 constructor(public el: ElementRef) { }

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

Use this function for Autofocus:-

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;
}
Dryad answered 20/11, 2022 at 8:7 Comment(0)
R
0

My solution:

import {
    AfterViewInit,
    Directive,
    ElementRef,
    Injector,
    afterNextRender,
    input,
} from '@angular/core';

@Directive({
    selector: '[appAutofocus]',
    standalone: true,
})
export class AutofocusDirective implements AfterViewInit {
    appAutofocus = input<string>();

    constructor(
        private host: ElementRef,
        private injector: Injector,
    ) {}

    ngAfterViewInit() {
        let element: HTMLElement | undefined;
        if (!this.appAutofocus()) {
            element = this.host.nativeElement;
        } else {
            element = this.host.nativeElement.querySelector(this.appAutofocus());
        }

        afterNextRender(
            () => {
                element?.focus();
            },
            {
                injector: this.injector,
            },
        );
    }
}

inspired from: https://github.com/angular/components/blob/ab17c4413ad0ab767eb3b61671cf0953dbcf4561/src/cdk/a11y/focus-trap/focus-trap.ts#L361

Readjust answered 22/5 at 13:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.