How to get `DOM Element` in Angular 2?

AngularDom

Angular Problem Overview


I have a component that has a <p> element. It's (click) event will change it into a <textarea>. So, the user can edit the data. My question is:

  • How can I make the focus on the textarea?
  • How can I get the element, so I can apply the .focus() on it?
  • Can I avoid using document.getElemenntById()?

I have tried to use the "ElementRef" and the "@ViewChild()" however it seems that I'm missing something:

app.component.ts

@ViewChild('tasknoteId') taskNoteRef:ElementRef;

noteEditMode: boolean = false;

get isShowNote (){
  return  !this.noteEditMode && this.todo.note  ? true : false;
}
taskNote: string;
toggleNoteEditMode () {
  this.noteEditMode = !this.noteEditMode; 
  this.renderer.invokeElementMethod(
    this.taskNoteRef.nativeElement,'focus'
  );
}

app.component.html

<span class="the-insert">
  <form [hidden]="!noteEditMode && todo.note">
    <textarea #tasknoteId id="tasknote"
     name="tasknote"
     [(ngModel)]="todo.note"
     placeholder="{{ notePlaceholder }}"
     style="background-color:pink"
     (blur)="updateNote()" (click)="toggleNoteEditMode()"
     [autofocus]="noteEditMode"
     [innerHTML]="todo.note">
   </textarea>
 </form>
</span>

Angular Solutions


Solution 1 - Angular

Use ViewChild with #TemplateVariable as shown here,

<textarea  #someVar  id="tasknote"
                  name="tasknote"
                  [(ngModel)]="taskNote"
                  placeholder="{{ notePlaceholder }}"
                  style="background-color: pink"
                  (blur)="updateNote() ; noteEditMode = false " (click)="noteEditMode = false"> {{ todo.note }} 

</textarea>
 

In component, #OLDEST Way

import {ElementRef} from '@angular/core';
@ViewChild('someVar') el:ElementRef;

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


#OLD Way

import {ElementRef} from '@angular/core';
@ViewChild('someVar') el:ElementRef;
    
constructor(private rd: Renderer) {}
ngAfterViewInit() {
    this.rd.invokeElementMethod(this.el.nativeElement,'focus');
}


Updated on 22/03(March)/2017 #NEW Way

Please note from Angular v4.0.0-rc.3 (2017-03-10) few things have been changed. Since Angular team will deprecate invokeElementMethod, above code no longer can be used.

>BREAKING CHANGES

>since 4.0 rc.1:

>rename RendererV2 to Renderer2
>rename RendererTypeV2 to RendererType2
>rename RendererFactoryV2 to RendererFactory2

import {ElementRef,Renderer2} from '@angular/core';
@ViewChild('someVar') el:ElementRef;

constructor(private rd: Renderer2) {}

ngAfterViewInit() {
      console.log(this.rd); 
      this.el.nativeElement.focus();      //<<<=====same as oldest way
}

console.log(this.rd) will give you following methods and you can see now invokeElementMethod is not there. Attaching img as yet it is not documented.

NOTE: You can use following methods of Rendere2 with/without ViewChild variable to do so many things.

enter image description here

Solution 2 - Angular

Update (using renderer):

> Note that the original Renderer service has now been deprecated in > favor of Renderer2

as on Renderer2 official doc.

Furthermore, as pointed out by @GünterZöchbauer:

> Actually using ElementRef is just fine. Also using > ElementRef.nativeElement with Renderer2 is fine. What is discouraged > is accessing properties of ElementRef.nativeElement.xxx directly.


You can achieve this by using elementRef as well as by ViewChild. however it's not recommendable to use elementRef due to:

  • security issue
  • tight coupling

as pointed out by official ng2 documentation.

#1. Using elementRef (Direct Access):

export class MyComponent {    
constructor (private _elementRef : ElementRef) {
 this._elementRef.nativeElement.querySelector('textarea').focus();
 }
}

#2. Using ViewChild (better approach):

<textarea  #tasknote name="tasknote" [(ngModel)]="taskNote" placeholder="{{ notePlaceholder }}" 
style="background-color: pink" (blur)="updateNote() ; noteEditMode = false " (click)="noteEditMode = false"> {{ todo.note }} </textarea> // <-- changes id to local var
      

export class MyComponent implements AfterViewInit {
  @ViewChild('tasknote') input: ElementRef;

   ngAfterViewInit() {
    this.input.nativeElement.focus();

  }
}

#3. Using renderer:

export class MyComponent implements AfterViewInit {
      @ViewChild('tasknote') input: ElementRef;
         constructor(private renderer: Renderer2){           
          }

       ngAfterViewInit() {
       //using selectRootElement instead of depreaced invokeElementMethod
       this.renderer.selectRootElement(this.input["nativeElement"]).focus();
      }

    }

Solution 3 - Angular

Angular 2.0.0 Final:

I have found that using a ViewChild setter is most reliable way to set the initial form control focus:

@ViewChild("myInput")
set myInput(_input: ElementRef | undefined) {
    if (_input !== undefined) {
        setTimeout(() => {
            this._renderer.invokeElementMethod(_input.nativeElement, "focus");
        }, 0);
    }
}

The setter is first called with an undefined value followed by a call with an initialized ElementRef.

Working example and full source here: http://plnkr.co/edit/u0sLLi?p=preview

Using TypeScript 2.0.3 Final/RTM, Angular 2.0.0 Final/RTM, and Chrome 53.0.2785.116 m (64-bit).

UPDATE for Angular 4+

Renderer has been deprecated in favor of Renderer2, but Renderer2 does not have the invokeElementMethod. You will need to access the DOM directly to set the focus as in input.nativeElement.focus().

I'm still finding that the ViewChild setter approach works best. When using AfterViewInit I sometimes get read property 'nativeElement' of undefined error.

@ViewChild("myInput")
set myInput(_input: ElementRef | undefined) {
    if (_input !== undefined) {
        setTimeout(() => { //This setTimeout call may not be necessary anymore.
            _input.nativeElement.focus();
        }, 0);
    }
}

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
Question&#223;astianView Question on Stackoverflow
Solution 1 - AngularNikhil ShahView Answer on Stackoverflow
Solution 2 - AngularcandidJView Answer on Stackoverflow
Solution 3 - AngularKTCOView Answer on Stackoverflow