How to get `DOM Element` in Angular 2?
AngularDomAngular 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.
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);
}
}