How can I select an element in a component template?
Solution 1
Instead of injecting ElementRef
and using querySelector
or similar from there, a declarative way can be used instead to access elements in the view directly:
<input #myname>
@ViewChild('myname') input;
element
ngAfterViewInit() {
console.log(this.input.nativeElement.value);
}
- @ViewChild() supports directive or component type as parameter, or the name (string) of a template variable.
-
@ViewChildren() also supports a list of names as comma separated list (currently no spaces allowed
@ViewChildren('var1,var2,var3')
). -
@ContentChild() and @ContentChildren() do the same but in the light DOM (
<ng-content>
projected elements).
descendants
@ContentChildren()
is the only one that allows to also query for descendants
@ContentChildren(SomeTypeOrVarName, {descendants: true}) someField;
{descendants: true}
should be the default but is not in 2.0.0 final and it's considered a bug
This was fixed in 2.0.1
read
If there are a component and directives the read
parameter allows to specify which instance should be returned.
For example ViewContainerRef
that is required by dynamically created components instead of the default ElementRef
@ViewChild('myname', { read: ViewContainerRef }) target;
subscribe changes
Even though view children are only set when ngAfterViewInit()
is called and content children are only set when ngAfterContentInit()
is called, if you want to subscribe to changes of the query result, it should be done in ngOnInit()
https://github.com/angular/angular/issues/9689#issuecomment-229247134
@ViewChildren(SomeType) viewChildren;
@ContentChildren(SomeType) contentChildren;
ngOnInit() {
this.viewChildren.changes.subscribe(changes => console.log(changes));
this.contentChildren.changes.subscribe(changes => console.log(changes));
}
direct DOM access
can only query DOM elements, but not components or directive instances:
export class MyComponent {
constructor(private elRef:ElementRef) {}
ngAfterViewInit() {
var div = this.elRef.nativeElement.querySelector('div');
console.log(div);
}
// for transcluded content
ngAfterContentInit() {
var div = this.elRef.nativeElement.querySelector('div');
console.log(div);
}
}
get arbitrary projected content
See Access transcluded content
Solution 2
You can get a handle to the DOM element via ElementRef
by injecting it into your component's constructor:
constructor(private myElement: ElementRef) { ... }
Docs: https://angular.io/docs/ts/latest/api/core/index/ElementRef-class.html
Solution 3
import { Component, ElementRef, OnInit } from '@angular/core';
@Component({
selector:'display',
template:`
<input (input)="updateName($event.target.value)">
<p> My name : {{ myName }}</p>
`
})
class DisplayComponent implements OnInit {
constructor(public element: ElementRef) {
this.element.nativeElement // <- your direct element reference
}
ngOnInit() {
var el = this.element.nativeElement;
console.log(el);
}
updateName(value) {
// ...
}
}
Example updated to work with the latest version
For more details on native element, here
Solution 4
Angular 4+:
Use renderer.selectRootElement
with a CSS selector to access the element.
I've got a form that initially displays an email input. After the email is entered, the form will be expanded to allow them to continue adding information relating to their project. However, if they are not an existing client, the form will include an address section above the project information section.
As of now, the data entry portion has not been broken up into components, so the sections are managed with *ngIf directives. I need to set focus on the project notes field if they are an existing client, or the first name field if they are new.
I tried the solutions with no success. However, Update 3 in this answer gave me half of the eventual solution. The other half came from MatteoNY's response in this thread. The result is this:
import { NgZone, Renderer } from '@angular/core';
constructor(private ngZone: NgZone, private renderer: Renderer) {}
setFocus(selector: string): void {
this.ngZone.runOutsideAngular(() => {
setTimeout(() => {
this.renderer.selectRootElement(selector).focus();
}, 0);
});
}
submitEmail(email: string): void {
// Verify existence of customer
...
if (this.newCustomer) {
this.setFocus('#firstname');
} else {
this.setFocus('#description');
}
}
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.
I know this example dealt primarily with the focus event, but it's hard for me that this couldn't be used in other contexts.
UPDATE: Angular dropped support for Renderer
in Angular 4 and removed it completely in Angular 9. This solution should not be impacted by the migration to Renderer2
. Please refer to this link for additional information:
Renderer migration to Renderer2
Solution 5
For people trying to grab the component instance inside a *ngIf
or *ngSwitchCase
, you can follow this trick.
Create an init
directive.
import {
Directive,
EventEmitter,
Output,
OnInit,
ElementRef
} from '@angular/core';
@Directive({
selector: '[init]'
})
export class InitDirective implements OnInit {
constructor(private ref: ElementRef) {}
@Output() init: EventEmitter<ElementRef> = new EventEmitter<ElementRef>();
ngOnInit() {
this.init.emit(this.ref);
}
}
Export your component with a name such as myComponent
@Component({
selector: 'wm-my-component',
templateUrl: 'my-component.component.html',
styleUrls: ['my-component.component.css'],
exportAs: 'myComponent'
})
export class MyComponent { ... }
Use this template to get the ElementRef
AND MyComponent
instance
<div [ngSwitch]="type">
<wm-my-component
#myComponent="myComponent"
*ngSwitchCase="Type.MyType"
(init)="init($event, myComponent)">
</wm-my-component>
</div>
Use this code in TypeScript
init(myComponentRef: ElementRef, myComponent: MyComponent) {
}
Aman Gupta
A passionate JavaScript developer by day, A humble guitar player by night .
Updated on July 08, 2022Comments
-
Aman Gupta almost 2 years
Does anybody know how to get hold of an element defined in a component template? Polymer makes it really easy with the
$
and$$
.I was just wondering how to go about it in Angular.
Take the example from the tutorial:
import {Component} from '@angular/core'; @Component({ selector:'display', template:` <input #myname (input)="updateName(myname.value)"/> <p>My name : {{myName}}</p> ` }) export class DisplayComponent { myName: string = "Aman"; updateName(input: String) { this.myName = input; } }
How do I catch hold or get a reference of the
p
orinput
element from within the class definition? -
Jefftopia over 8 years@Brocco can you update your answer? I'd like to see a current solution since
ElementRef
is gone. -
Günter Zöchbauer over 8 years
ElementRef
is available (again?). -
Honorable Chow about 8 yearsThe angular teams advised against using ElementRef, this is the better solution.
-
Günter Zöchbauer about 8 yearsActually
input
also is anElementRef
, but you get the reference to the element you actually want, instead of querying it from the hostElementRef
. -
Günter Zöchbauer almost 8 yearsActually using
ElementRef
is just fine. Also usingElementRef.nativeElement
withRenderer
is fine. What is discouraged is accessing properties ofElementRef.nativeElement.xxx
directly. -
Natanael almost 8 yearsI need to change the className of a child element (html element inside the template) of the component. I made this way: constructor(private myElement: ElementRef)... ngOnInit() { var el = this.myElement.nativeElement; el.children[0].className = 'new-class-name'; }. Is it correct? I was not able to retrieve the element using @ViewChild().
-
Natanael almost 8 yearsGünter, where is is said it's discouraged?
-
Günter Zöchbauer almost 8 years@Natanael I don't know if or where this is explicitly documented but it is mentioned regularly in issues or other discussions (also from Angular team members) that direct DOM access should be avoided. Accessing the DOM directly (which is what accessing properties and methods of
ElementRef.nativeElement)
is, prevents you from using Angulars server side rendering and WebWorker feature (I don't know if it also breaks the upcoming offline template compiler - but I guess not). -
Natanael almost 8 yearsIn some situations I need to define a value to an property like a class without use binding because binding can avoid other libraries to change the elements properties. The only way I found to do this is via direct property edition.
-
Günter Zöchbauer almost 8 yearsYou can do that. You just need to be aware of the disadvantages though.
-
jsgoupil almost 8 yearsAs mentioned above in the read section, if you want to get the nativeElement for an element with ViewChild, you have to do the following:
@ViewChild('myObj', { read: ElementRef }) myObj: ElementRef;
-
KTCO over 7 yearsI have found that using a
ViewChild
setter works reliably: stackoverflow.com/a/39679241/882912 -
Luke T O'Brien over 7 yearsThis is a good description of
ViewChild
- However the question is asking how to get the<p>
element - In jQuery we could do$('p')
or$(input).next('p')
, how do we do this in Angular 2? How do we get the DOM element from theViewChild
? -
Günter Zöchbauer over 7 yearsYou can add a template variable and use
@ViewChild('varName')
or injectElementRef
and usethis.elRef.nativeElement.querySelector('p')
-
rayryeng over 7 yearsPlease provide some explanation to this code. Simply code dumping without explanation is highly discouraged.
-
Max Koretskyi over 7 years@GünterZöchbauer, if you want to subscribe to changes of the query result, it should be done in ngOnInit() - I assume it's not yet implemented, correct? In
ngOnInit
the viewchildren property is not yet initialized -
Günter Zöchbauer over 7 yearsI saw these comments, not exactly sure what they mean.
-
oooyaya about 7 yearsThis explains a problem I was having. This doesn't work because it'll say
item
needs to be an ElementRef, even though you're setting it to another ElementRef:let item:ElementRef, item2:ElementRef; item = item2; // no can do.
. Very confusing. But this is fine:let item:ElementRef, item2:ElementRef; item = item2.nativeElement
because of the implementation you pointed out. -
Aluan Haddad about 7 yearsActually your first example
let item: ElementRef, item2: ElementRef; item = item2
fails because of definite assignment analysis. Your second fails for the same reasons but both succeed ifitem2
is initialized for the reasons discussed (or as a useful quick check for assignability we can usedeclare let
here). Regardless, truly a shame to seeany
on a public API like this. -
David M. about 7 yearsThis won't work: attributes set via @ViewChild annotations will only be available after ngAfterViewInit lifecycle event. Accessing the value in the constructor would yield an undefined value for
inputTxt
in that case. -
sandeep talabathula about 7 yearslink Use this API as the last resort when direct access to DOM is needed. Use templating and data-binding provided by Angular instead. Alternatively you take a look at Renderer which provides API that can safely be used even when direct access to native elements is not supported. Relying on direct DOM access creates tight coupling between your application and rendering layers which will make it impossible to separate the two and deploy your application into a web worker.
-
superjos about 7 yearsHow to get a hold of a native element from within a ContentChild? Tried with
@ContentChild(SomeComponent, { read: ElementRef })
but it did not work. It complained with Can't construct a query for the property ... of ... since the query selector wasn't defined -
superjos about 7 years(I mean, beside trashing
@ContentChild
and turning to discouragedElementRef.nativeElement.querySelector()
) -
Jamie almost 7 yearsThe class Renderer is DEPRECATED since Angular 4.3.0. angular.io/api/core/Renderer
-
ProgrammingLlama almost 7 years@sandeeptalabathula What is a better option for finding an element to attach a floating date picker component from a third-party library to? I'm aware that this wasn't the original question, but you make it out that finding elements in the DOM is bad in all scenarios...
-
sandeep talabathula almost 7 years@john In my perspective, better option would be to create a custom angular2 ts component (an adapter to 3rd party lib) and use it as reusable throughout your app. I did for dialogs, flyouts etc,.
-
ProgrammingLlama almost 7 years@sandeeptalabathula That's what I've been doing, but it needed me to pass a DOM element to it.
-
sandeep talabathula almost 7 years@john Ah.. okay. You may try out this -
this.element.nativeElement.querySelector('#someElementId')
and pass ElementRef to the constructor like this..private element: ElementRef,
Import lib...import { ElementRef } from '@angular/core';
-
Hany over 6 yearsBut you should notice that you almost not need to access template elements in the component class, you just need to well understand the angular logic correctly.
-
Johannes over 6 yearsDont use any, the type is ElementRef
-
Celso Soares over 5 yearsWhat if you want to access to a dynamic class or id for example? THis seems to be good for static elements
-
Günter Zöchbauer over 5 yearsYou can inject
ElementRef
to get a reference to the host element of the component and usequerySelector
or any other method DOMElement
provides. There is no Angular way to access elements added dynamically through imperative DOM manipulation. -
iGanja over 4 yearsnew to Angular; so let me get this straight... this.elRef.nativeElement.querySelector('div'); is better than $('div'); Got It! And to think; we used to create web sites in HTML and [gulp] JavaScript...
-
Günter Zöchbauer over 4 yearsIf you use
this.elRef.nativeElement.querySelector('div')
you are probably trying to use Angular as a jQuery alternative. Angular is wildly different. There are only rare cases where you need such code, for example if you integrating legazy (jQuery or similar components). In pure Angular bindings and services usually do the job. -
Tharindu Sathischandra over 3 yearswith
@ViewChild('myname') input;
, Do we need to use nativeElement property likethis.input.nativeElement.value
?? Isn'tthis.input.value
just enough? -
Günter Zöchbauer over 3 years@TharinduSathischandra that's quite an old answer and I haven't used this in s long time. It should be easy enough to figure out.
-
Jordan over 3 yearsCan't we just use Renderer2 @Jamie ? https://angular.io/api/core/Renderer2#selectRootElement
-
TomEberhard over 3 yearsI ended up having to use 'any' because the element that I needed access to was another angular component which was wrapping a Kendo UI element, I needed to call a method on the component, which then calls a method on the Kendo element.
-
GBra 4.669 over 3 years
ElementRef
API should be the last resort when direct access to DOM is needed.Renderer2
should be the preferred way. UsingElementRef
might be a potential security risk!! angular.io/api/core/ElementRef#properties