Set focus on <input> element

AngularAngular5Angular Forms

Angular Problem Overview


I am working a front-end application with Angular 5, and I need to have a search box hidden, but on click of a button, the search box should be displayed and focused.

I have tried a few ways found on StackOverflow with directive or so, but can't succeed.

Here is the sample code:

@Component({
   selector: 'my-app',
   template: `
    <div>
    <h2>Hello</h2>
    </div>
    <button (click) ="showSearch()">Show Search</button>
    <p></p>
    <form>
      <div >
        <input *ngIf="show" #search type="text"  />            
      </div>
    </form>
    `,
  })
  export class App implements AfterViewInit {
  @ViewChild('search') searchElement: ElementRef;

  show: false;
  name:string;
  constructor() {    
  }

  showSearch(){
    this.show = !this.show;    
    this.searchElement.nativeElement.focus();
    alert("focus");
  }

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

The search box is not set to focus.

How can I do that?

Angular Solutions


Solution 1 - Angular

Modify the show search method like this

showSearch(){
  this.show = !this.show;  
  setTimeout(()=>{ // this will make the execution after the above boolean has changed
	this.searchElement.nativeElement.focus();
  },0);  
}

Solution 2 - Angular

You should use HTML autofocus for this:

<input *ngIf="show" #search type="text" autofocus /> 

Note: if your component is persisted and reused, it will only autofocus the first time the fragment is attached. This can be overcome by having a global DOM listener that checks for autofocus attribute inside a DOM fragment when it is attached and then reapplying it or focus via JavaScript.

Here is an example global listener, it only needs to be placed in your spa application once and autofocus will function regardless of how many times the same fragment is reused:

(new MutationObserver(function (mutations, observer) {
    for (let i = 0; i < mutations.length; i++) {
        const m = mutations[i];
        if (m.type == 'childList') {
            for (let k = 0; k < m.addedNodes.length; k++) {
                const autofocuses = m.addedNodes[k].querySelectorAll("[autofocus]"); //Note: this ignores the fragment's root element
                console.log(autofocuses);
                if (autofocuses.length) {
                    const a = autofocuses[autofocuses.length - 1]; // focus last autofocus element
                    a.focus();
                    a.select();
                }
            }
        }
    }
})).observe(document.body, { attributes: false, childList: true, subtree: true });

Solution 3 - Angular

This directive will instantly focus and select any text in the element as soon as it's displayed. This might require a setTimeout for some cases, it has not been tested much.

import { Directive, ElementRef, OnInit } from '@angular/core';
    
@Directive({
  selector: '[appPrefixFocusAndSelect]',
})
export class FocusOnShowDirective implements OnInit {    
  constructor(private el: ElementRef) {
    if (!el.nativeElement['focus']) {
      throw new Error('Element does not accept focus.');
    }
  }
    
  ngOnInit(): void {
    const input: HTMLInputElement = this.el.nativeElement as HTMLInputElement;
    input.focus();
    input.select();
  }
}

And in the HTML:

<mat-form-field>
  <input matInput type="text" appPrefixFocusAndSelect [value]="'etc'">
</mat-form-field>

Solution 4 - Angular

I'm going to weigh in on this (Angular 7 Solution)

input [appFocus]="focus"....
import {AfterViewInit, Directive, ElementRef, Input,} from '@angular/core';

@Directive({
  selector: 'input[appFocus]',
})
export class FocusDirective implements AfterViewInit {

  @Input('appFocus')
  private focused: boolean = false;

  constructor(public element: ElementRef<HTMLElement>) {
  }

  ngAfterViewInit(): void {
	// ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked.
	if (this.focused) {
	  setTimeout(() => this.element.nativeElement.focus(), 0);
	}
  }
}

Solution 5 - Angular

This is working i Angular 8 without setTimeout:

import {AfterContentChecked, Directive, ElementRef} from '@angular/core';

@Directive({
  selector: 'input[inputAutoFocus]'
})
export class InputFocusDirective implements AfterContentChecked {
  constructor(private element: ElementRef<HTMLInputElement>) {}

  ngAfterContentChecked(): void {
    this.element.nativeElement.focus();
  }
}

Explanation: Ok so this works because of: Change detection. It's the same reason that setTimout works, but when running a setTimeout in Angular it will bypass Zone.js and run all checks again, and it works because when the setTimeout is complete all changes are completed. With the correct lifecycle hook (AfterContentChecked) the same result can be be reached, but with the advantage that the extra cycle won't be run. The function will fire when all changes are checked and passed, and runs after the hooks AfterContentInit and DoCheck. If i'm wrong here please correct me.

More one lifecycles and change detection on https://angular.io/guide/lifecycle-hooks

UPDATE: I found an even better way to do this if one is using Angular Material CDK, the a11y-package. First import A11yModule in the the module declaring the component you have the input-field in. Then use cdkTrapFocus and cdkTrapFocusAutoCapture directives and use like this in html and set tabIndex on the input:

<div class="dropdown" cdkTrapFocus cdkTrapFocusAutoCapture>
    <input type="text tabIndex="0">
</div>

We had some issues with our dropdowns regarding positioning and responsiveness and started using the OverlayModule from the cdk instead, and this method using A11yModule works flawlessly.

Solution 6 - Angular

In Angular, within HTML itself, you can set focus to input on click of a button.

<button (click)="myInput.focus()">Click Me</button>

<input #myInput></input>

Solution 7 - Angular

To make the execution after the boolean has changed and avoid the usage of timeout you can do:

import { ChangeDetectorRef } from '@angular/core';

constructor(private cd: ChangeDetectorRef) {}

showSearch(){
  this.show = !this.show;  
  this.cd.detectChanges();
  this.searchElement.nativeElement.focus();
}

Solution 8 - Angular

html of component:

<input [cdkTrapFocusAutoCapture]="show" [cdkTrapFocus]="show">

controler of component:

showSearch() {
  this.show = !this.show;    
}

..and do not forget about import A11yModule from @angular/cdk/a11y

import { A11yModule } from '@angular/cdk/a11y'

Solution 9 - Angular

There is also a DOM attribute called cdkFocusInitial which works for me on inputs. You can read more about it here: https://material.angular.io/cdk/a11y/overview

Solution 10 - Angular

I'm having same scenario, this worked for me but i'm not having the "hide/show" feature you have. So perhaps you could first check if you get the focus when you have the field always visible, and then try to solve why does not work when you change visibility (probably that's why you need to apply a sleep or a promise)

To set focus, this is the only change you need to do:

your Html mat input should be:

<input #yourControlName matInput>

in your TS class, reference like this in the variables section (

export class blabla...
    @ViewChild("yourControlName") yourControl : ElementRef;

Your button it's fine, calling:

  showSearch(){
       ///blabla... then finally:
       this.yourControl.nativeElement.focus();
}

and that's it. You can check this solution on this post that I found, so thanks to --> https://codeburst.io/focusing-on-form-elements-the-angular-way-e9a78725c04f

Solution 11 - Angular

Only using Angular Template

<input type="text" #searchText>

<span (click)="searchText.focus()">clear</span>

Solution 12 - Angular

Easier way is also to do this.

let elementReference = document.querySelector('<your css, #id selector>');
    if (elementReference instanceof HTMLElement) {
        elementReference.focus();
    }

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBobView Question on Stackoverflow
Solution 1 - AngularArvind MuthuramanView Answer on Stackoverflow
Solution 2 - AngularN-ateView Answer on Stackoverflow
Solution 3 - AngularggranumView Answer on Stackoverflow
Solution 4 - AngularRicardo SaracinoView Answer on Stackoverflow
Solution 5 - AngularSNDVLLView Answer on Stackoverflow
Solution 6 - Angularshaheer shukurView Answer on Stackoverflow
Solution 7 - AngularMarcin RestelView Answer on Stackoverflow
Solution 8 - AngularCichyView Answer on Stackoverflow
Solution 9 - AngularTimonView Answer on Stackoverflow
Solution 10 - AngularYogurtuView Answer on Stackoverflow
Solution 11 - AngularSandipan MitraView Answer on Stackoverflow
Solution 12 - AngularpatzView Answer on Stackoverflow