49

I am new to Angular, and am trying to use it to set focus on an input with the id "input1". I am using the following code:

@ViewChild('input1') inputEl: ElementRef;

then later in the component:

 this.inputEl.nativeElement.focus();

But it isn't working. What am I doing wrong? Any help will be much appreciated.

Matthijs
  • 2,483
  • 5
  • 22
  • 33
Pismotality
  • 2,149
  • 7
  • 24
  • 30
  • https://stackoverflow.com/questions/38307060/how-to-set-focus-on-element-with-binding – Z. Bagley Oct 13 '17 at 00:10
  • The input is dynamically generated as part of a PrimeNg template. I use jquery to set the id on the element, but I have no idea how to set [focus] on it. Is there another way? – Pismotality Oct 13 '17 at 00:13
  • Thank you @Z.Bagley! One of the answers in the question you referred to had the solution. – Pismotality Oct 13 '17 at 02:52

8 Answers8

70

Component

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

@ViewChild('input1', {static: false}) inputEl: ElementRef;
    
ngAfterViewInit() {
   setTimeout(() => this.inputEl.nativeElement.focus());
}

HTML

<input type="text" #input1>
Dmitry Grinko
  • 13,806
  • 14
  • 62
  • 86
  • 1
    Could you explain why the setTimeout is needed? That seems counter-intuitive if the code is running *after* the view is init'ed. Is it a common pitfall? – ANeves Jan 09 '19 at 19:18
  • 2
    @ANeves Stijn Van Bael has added `setTimeout()` to prevent `ExpressionChangedAfterItHasBeenCheckedError` – Dmitry Grinko Jan 09 '19 at 21:51
  • 1
    @DmitryGrinko - setTimout() adding little delay hence focus is going to next field and again coming back to the next field before previous one. Do we have any alternative to setTimout()? – Sagar Ganesh Apr 08 '19 at 10:31
  • @Shree Remove setTimeout and if that error occurs, try to look for another solution. There are a lot info about this error. Unfortunately I have no much time to reproduce this case. – Dmitry Grinko Apr 08 '19 at 21:42
  • In Angular 9 you'll need to set the `static` option too e.g. `@ViewChild('input1', {static: false})` – Mike Poole Jul 29 '20 at 14:47
  • 1
    @DmitryGrinko it looks like you already did that just a couple of hours after I commented. – Mike Poole Jan 22 '21 at 16:17
30

One of the answers in the question referred to by @Z.Bagley gave me the answer. I had to import Renderer2 from @angular/core into my component. Then:

const element = this.renderer.selectRootElement('#input1');
setTimeout(() => element.focus(), 0);

Thank you @MrBlaise for the solution!

Eladigo
  • 7
  • 4
Pismotality
  • 2,149
  • 7
  • 24
  • 30
  • 7
    The setTimeout is the key here. You can also get the element without the renderer by annotating an ElementRef property with the ViewChild annotation and then referencing the 'nativeElement' property of that ElementRef. – GreyBeardedGeek Mar 15 '18 at 14:59
  • @GreyBeardedGeek Although using ElementRef has XSS security risks, so Dwight's solution is probably safer. See: https://angular.io/api/core/ElementRef – rmcsharry Mar 27 '18 at 18:25
  • This is also useful to be aware of: https://github.com/angular/angular/issues/15674 – rmcsharry Mar 27 '18 at 18:28
8

I also face same issue after some search I found a good solution as @GreyBeardedGeek mentioned that setTimeout is the key of this solution.He is totally correct. In your method you just need to add setTimeout and your problem will be solved.

setTimeout(() => this.inputEl.nativeElement.focus(), 0);
Abhijeet
  • 799
  • 6
  • 13
6

Here is an Angular4+ directive that you can re-use in any component. Based on code given in the answer by Niel T in this question.

import { NgZone, Renderer, Directive, Input } from '@angular/core';

@Directive({
    selector: '[focusDirective]'
})
export class FocusDirective {
    @Input() cssSelector: string

    constructor(
        private ngZone: NgZone,
        private renderer: Renderer
    ) { }

    ngOnInit() {
        console.log(this.cssSelector);
        this.ngZone.runOutsideAngular(() => {
            setTimeout(() => {
                this.renderer.selectRootElement(this.cssSelector).focus();
            }, 0);
        });
    }
}

You can use it in a component template like this:

<input id="new-email" focusDirective cssSelector="#new-email"
  formControlName="email" placeholder="Email" type="email" email>

Give the input an id and pass the id to the cssSelector property of the directive. Or you can pass any cssSelector you like.

Comments from Niel T:

Since the only thing I'm doing is setting the focus on an element, I don't need to concern myself with change detection, so I can actually run the call to renderer.selectRootElement outside of Angular. Because I need to give the new sections time to render, the element section is wrapped in a timeout to allow the rendering threads time to catch up before the element selection is attempted. Once all that is setup, I can simply call the element using basic CSS selectors.

rmcsharry
  • 5,363
  • 6
  • 65
  • 108
4

This helped to me (in ionic, but idea is the same) https://mhartington.io/post/setting-input-focus/

in template:

<ion-item>
      <ion-label>Home</ion-label>
      <ion-input #input type="text"></ion-input>
</ion-item>
<button (click)="focusInput(input)">Focus</button>

in controller:

  focusInput(input) {
    input.setFocus();
  }
3

Here is a directive that you can use in any component:

import { NgZone, Directive, ElementRef, AfterContentInit, Renderer2 } from '@angular/core';

@Directive({
    selector: '[appFocus]'
})
export class FocusDirective implements AfterContentInit {
    constructor(private el: ElementRef, private zone: NgZone, private renderer: Renderer2) {}

    ngAfterContentInit() {
        this.zone.runOutsideAngular(() => setTimeout(() => {
            this.renderer.selectRootElement(this.el.nativeElement).focus();
        }, 0));
    }
}

Use:

<input type="text" appFocus>
ACDev
  • 31
  • 2
0

You can simply use scrollIntoView as shown in the example below and call the method whenever you like.

<div id="elementID"></div>
ngAfterViewInit(): void 
{
    const element = document.querySelector("#elementID");
    element.scrollIntoView(true);
}
Tim
  • 5,435
  • 7
  • 42
  • 62
Gina Habib
  • 21
  • 1
  • 5
0

Add in your component

ngOnInit(): void {
    document?.getElementById("elementToGiveFocus")?.focus();
}