13

I updated from Angular 2 to Angular 4 and in the docs it's written to use the Renderer2 instead of Renderer which is deprecated.

Now am looking into the Renderer source, but cannot find a way to invoke the focus() method as I used to.

Old method:

this.renderer.invokeElementMethod(element, 'focus', []);

What is the new aproach?

EDIT

What if the element i am focusing onto is obtained via QuerySelector?

For instance:

let inputField = document.querySelectorAll('.dialog input');
if ( inputField[0] ) {
   inputField[0].focus();
}

Since its obtained via QuerySelector, the focus() method doesn't exist on it.

Wictor Chaves
  • 957
  • 1
  • 13
  • 21
Hassan
  • 2,308
  • 5
  • 21
  • 31

6 Answers6

20

The invokeElementMethod is deprecated, and will not find its way back into the new renderer. To set focus to an element, you can simply use this now:

element.nativeElement.focus();

If you are using document.querySelectorAll, you are doing something not the angular way, and you should find another way to do it. If there is no other way to do it, then the same principle applies. The focus() method is plain javascript, so you can use it within angular2/typescript. Be sure to do the querySelectorAll inside the ngAfterViewInit hook of the component though:

ngAfterViewInit() {
   let inputField: HTMLElement = <HTMLElement>document.querySelectorAll('.dialog input')[0];
   inputField && inputField.focus();
}
Poul Kruijt
  • 69,713
  • 12
  • 145
  • 149
  • @Hassan I already updated my answer, because I realised as much. `querySelectorAll` returns an array of `Element`, but you can probably be sure that it's an `HTMLElement`, so you can type hint it to such, so that TypeScript won't complain – Poul Kruijt Mar 24 '17 at 11:40
  • Yeah it now works perfectly fine with your updated answer. Thank you very much! :) – Hassan Mar 24 '17 at 11:42
  • 3
    shouldn't we avoid calling focus directly on nativeElement as we are not using Angular's abstraction layer properly then. Instead should we use Renderer2 APIs? – Manu Chadha Aug 04 '18 at 16:32
  • @ManuChadha there is no method on the renderer2 to do this :) but perhaps a template ref on the element in the html self and call focus() on that is more angulary – Poul Kruijt Aug 04 '18 at 17:04
  • I agree. I am trying to code the Angular way and also trying to keep abstraction intact but unfortunately the framework isn't as abstract as I thought it would be. Still very new in Angular so I might be wrong – Manu Chadha Aug 04 '18 at 17:07
15

Also you can use selectRootElement method of Renderer2.

For example:

constructor(private renderer: Renderer2) {}

this.renderer.selectRootElement('#domElementId').focus()

, where domElementId is id='domElementId' in your html

seytzhan
  • 527
  • 5
  • 7
  • Thanks buddy Renderer2 api selectRootElement worked for me this.renderer.selectRootElement(this.email.nativeElement).focus(); element.nativeElement.focus(); // not working for me – Hemant Kumar Singh Mar 25 '18 at 06:37
  • Not a recommended approach -- https://github.com/angular/angular/issues/19554 says, "selectRootElement() is not intended for general use and, as you surmise, and really only useful in bootstrap." – Tim Jan 03 '20 at 15:20
6
template reference variable :#inlineEditControl
<input #inlineEditControl class="form-control form-control-lg"  [placeholder]="label">

 @ViewChild('inlineEditControl') inlineEditControl: ElementRef; // input DOM element

this.inlineEditControl.nativeElement.focus();
Vikas Mahajan
  • 345
  • 1
  • 7
  • Sorry, i have updated my question with the `document.querySelectorAll` method since the field i want to focus should be dynamically selected. – Hassan Mar 24 '17 at 11:32
  • 1
    Again, shouldn't we avoid calling focus directly on nativeElement as we are not using Angular's abstraction layer properly then. Instead should we use Renderer2 APIs? – Manu Chadha Aug 04 '18 at 16:33
4

If you are using Angular CDK, you can set focus using FocusMonitor.focusVia method that is part of @angular/cdk/a11y Accessibility module (A11yModule).

This way you can avoid any DOM manipulation and avoid referencing nativeElement altogether.


import { FocusMonitor } from '@angular/cdk/a11y';


export class ExampleComponent implements AfterViewInit {
  @ViewChild('elem', {static: false}) elemRef;

  constructor(private focusMonitor: FocusMonitor) {
  }

  ngAfterViewInit() {
    // Programmatically set focus. Focus source is 'program'.
    this.focusMonitor.focusVia(this.elemRef, 'program');
  }
}
Chen.so
  • 173
  • 7
3

At this time, it appears that this task is not achievable without directly modifying the DOM. As others have said, the invokeElementMethod() is deprecated. However, you will find issues with using selectRootElement as well, as this is not its intended purpose and you will end up losing the children inside of your DIV. Here is a link to a SO question that explains selectRootElement in more detail (since the angular docs are atrocious):

Renderer multiple selectRootElement Issue (with plnkr provided)

As for now, it appears the only way to easily achieve your goal is to use a template reference variable, a view child, and the yourViewChild.nativeElement.focus() (though this practice is also not recommended)

There is also a suggested workaround using a FocusService found on this GitHub issue: https://github.com/angular/angular/issues/15674

In this issue, Tytskyi mentions that you can implement a FocusService and provide different implementations of focus() based on AppModuleBrowser and AppModuleServer. I'm not sure of the fine details of how that would work but it may be the only way to achieve this without using nativeElement at this time.

Ben Atlas
  • 117
  • 2
  • 11
  • Thanks. I was looking for this explanation. I know that we should use Renderer2 but I couldn't find an example on how to use Renderer2 to focus on an input. Wondering, why such method isn't available in Renderer2 considering that is seems quite useful. Any idea? – Manu Chadha Aug 04 '18 at 16:36
2

If you declare the correct type of your inputField var, you can use any JS query selector.

For instances declaring:
let inputField: HTMLInputElement = document.querySelector('.dialog input');
enables you to call
inputField.focus();
that it will work and no TS error will be thrown.

Pedro Ferreira
  • 493
  • 7
  • 14