Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get `DOM Element` in Angular 2? [duplicate]

Tags:

dom

angular

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>
like image 950
ßastian Avatar asked Oct 09 '22 09:10

ßastian


3 Answers

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

like image 219
Nikhil Shah Avatar answered Oct 22 '22 10:10

Nikhil Shah


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();
      }

    }
like image 97
candidJ Avatar answered Oct 22 '22 10:10

candidJ


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);
    }
}
like image 11
KTCO Avatar answered Oct 22 '22 09:10

KTCO