16

Similar to Google Login page, I want to have autofocus on input element after on click event. I have tried @ViewChild('id') and document.getElementId('id'). Both of it doesn't work. It's always null or undefined. How can I achieve this?

       <mat-form-field class="example-full-width col-sm-4">
        <input autofocus id="login-username" matInput 
       formControlName="userName" minlength="5" maxlength="20" 
       name="userName" placeholder="Username" required #input> 
      </mat-form-field>

        <div class="col-sm-12">
           <button (click)="changeView()" type="button" mat-raised-
             button color="primary">Next</button>
          </div>

         <mat-form-field class="example-full-width col-sm-4" 
          #passwordInput>
        <input autofocus type="password" matInput 
       placeholder="Password" minlength="5" maxlength="20" 
       formControlName="userPassword" #passwordInput>  
      </mat-form-field>
Srihari GouthamGr
  • 298
  • 2
  • 4
  • 13
  • Pl show some code from where in HTML you are referencing @ViewChild – Gags Jul 25 '18 at 04:45
  • I am declaring @ViewChild('passwordInput') before constructor and using it in the changeView() when called. like this. changeView(){ this.passwordInputRef.nativeElement.focus(); } But this throws error. Says nativeElement is undefined. – Srihari GouthamGr Jul 25 '18 at 05:05
  • ViewChild properties aren't set until the AfterViewInit lifecycle hook – DeborahK Jul 25 '18 at 05:10
  • 1
    you need to add a `timeout` before setting `focus()`, or it will throw `nativeElement` `undefined` error – Akhil Aravind Jul 25 '18 at 05:30

6 Answers6

25

Unfortunately not every solution consistently works with autofocusing matInput at the moment of rendering. Here is what I tried:

  • HTML autofocus attribute
  • JS input.focus()
  • cdkFocusInitial from @angular/cdk

All of the above methods might work but under some conditions they appear to be broken.

What consistently and always works is using a high-level api of the matInput. Here is a simple directive that uses this approach:

import { Directive, OnInit } from '@angular/core';
import { MatInput } from '@angular/material/input';

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

  constructor(private matInput: MatInput) { }

  ngOnInit() {
    setTimeout(() => this.matInput.focus());
  }

}

The timeout is required to delay focusing the element because matInput does not properly function at the moment of creating yet. Usage:

<mat-form-field>
  <input type="text" matInput matInputAutofocus>
</mat-form-field>

Of course, naming the selector matInputAutofocus is dangerous because material itself can come to this solution one day. Use it on your own risk or just rename with your prefix (recommended).

Focus and meanwhile select the input value

A cherry on the cake is adding the possibility to also preselect the content of the input (this is most of the time more user-friendly), which slightly changes the implementation:

import { Directive, OnInit } from '@angular/core';
import { MatInput } from '@angular/material/input';
    
@Directive({
  selector: '[matInputAutofocus]',
})
export class AutofocusDirective implements OnInit {

  @Input()
  autofocusSelectValue = false;

  constructor(
    private matInput: MatInput,
    private elRef: ElementRef<HTMLInputElement>,
  ) { }

  ngOnInit(): void {
    setTimeout(() => {
      this.matInput.focus();

      if (this.autofocusSelectValue) {
        this.elRef.nativeElement.setSelectionRange(0, this.elRef.nativeElement.value.length);
      }
    });
  }

}

Use it as:

<mat-form-field>
  <input type="text" matInput matInputAutofocus [autofocusSelectValue]="true">
</mat-form-field>
smnbbrv
  • 23,502
  • 9
  • 78
  • 109
  • Another advantage of this solution over using accessing ElementRef using `.nativeElement.focus()` in `ngAfterViewInit()` is that you won't need to write ngAfterViewInit() for every component that uses `matInput` in it's template. – James Antony Jul 10 '19 at 11:26
  • 1
    MatInput import should be: import { MatInput } from '@angular/material/input'; – grim_i_am Jun 17 '20 at 22:52
  • @smnbbrv When I use this on an array of mat-form-fields created by a *ngFor-Loop, the focus is always set on the last element of the array. Do you have any idea how to fix that ? – Phreneticus Feb 22 '21 at 15:06
  • @Phreneticus I guess the easiest is to add `@Input('matInputAutofocus') enabled: boolean;`, run the ngOnInit part only in case it is true, and in ngFor set the property to true if it is first (or another desired location), something like `
    ...[matInputAutofocus]="first"`
    – smnbbrv Feb 22 '21 at 19:00
  • 1
    Because this answer still works well, I'll add another suggested typo fix. Instead of `input.value.length`, you want `this.matInput.value.length`. – Trevor Price May 09 '23 at 05:50
9

When you use Angular Material, you can use cdkFocusInitial to tell the library where you want to focus after component is ready. No native approaches are required:

<mat-form-field class="example-full-width">
  <mat-label>My Label</mat-label>
  <textarea 
    cdkFocusInitial           <---------
    cdkTextareaAutosize
    matInput>
  </textarea>
</mat-form-field>
J.Wincewicz
  • 849
  • 12
  • 19
8

If you want to set focus to an Input field as soon as the page loads, you can do it simply by applying HTML attribute to that element, such as :

<input type="text" #input1 autofocus>

Else if you want to do this from your component.ts conditionally, use:

    import { Component, ElementRef, ViewChild, AfterViewInit} from 
    '@angular/core';
    ... 

 @ViewChild('input1') inputEl:ElementRef;

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

Or you can use:

First import the renderer2 from @angular/core and then,

    const element = this.renderer.selectRootElement('#input1');

 setTimeout(() => element.focus(), 0);

Whatever you seems to be comfortable with your existing code.

Saket
  • 836
  • 6
  • 13
4

You can use cdkFocusInitial from Material.

<mat-form-field>
    <input matInput placeholder="username" cdkFocusInitial>
</mat-form-field>

See orginal answer.

Egoulet
  • 63
  • 2
  • 5
0

You need to use template ref to input element then use ViewChild it will work

Example: https://stackblitz.com/edit/angular-frqm8b

 <mat-form-field class="example-full-width col-sm-4">
        <input autofocus id="login-username" matInput 
       formControlName="userName" minlength="5" maxlength="20" 
       name="userName" placeholder="Username" required #input> 
      </mat-form-field>

        <div class="col-sm-12">
           <button (click)="changeView()" type="button" mat-raised-
             button color="primary">Next</button>
          </div>

         <mat-form-field class="example-full-width col-sm-4" 
          >
        <input autofocus type="password" matInput 
        ****#passwordInput****
       placeholder="Password" minlength="5" maxlength="20" 
       formControlName="userPassword" #passwordInput>  
      </mat-form-field>
Chellappan வ
  • 23,645
  • 3
  • 29
  • 60
0

I did a mixture of all the other answers. This was the only way it worked for me.

Add to any Typescript function

  @ViewChild('foobarElement') foobarElement: ElementRef;
      
  anyFunction() {
      setTimeout(() => this.foobarElement.nativeElement.focus());
  }

And in the HTML

  <input type="text"
    matInput 
    #foobarElement>
Kevin Oswaldo
  • 478
  • 5
  • 13