Makla
Makla

Reputation: 10459

Angular2+ autofocus input element

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.

Upvotes: 82

Views: 120135

Answers (13)

Ev357
Ev357

Reputation: 36

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

Upvotes: 0

Mukul Raghav
Mukul Raghav

Reputation: 489

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

Upvotes: 0

Salem Ouerdani
Salem Ouerdani

Reputation: 7886

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.

Upvotes: 4

Slava Fomin II
Slava Fomin II

Reputation: 28611

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).

Upvotes: 3

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!

Upvotes: 14

Rhumain Vermo
Rhumain Vermo

Reputation: 1

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.

Upvotes: 0

Tim.Burnell
Tim.Burnell

Reputation: 391

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.

Upvotes: 1

Try this simple but effective function.:

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

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

Upvotes: 1

Maximilian Riegler
Maximilian Riegler

Reputation: 23506

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

Upvotes: 24

Sergey Gurin
Sergey Gurin

Reputation: 1563

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

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

Upvotes: 26

Makla
Makla

Reputation: 10459

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

Upvotes: 103

Hampus
Hampus

Reputation: 2799

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.

Upvotes: 51

Michael Lang
Michael Lang

Reputation: 1180

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.

Upvotes: 23

Related Questions