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
or input
element from within the class definition?
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 (
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
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
ElementRef
is gone.
ElementRef
is available (again?).
this.element.nativeElement.querySelector('#someElementId')
and pass ElementRef to the constructor like this.. private element: ElementRef,
Import lib... import { ElementRef } from '@angular/core';
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
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
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) {
}
import the ViewChild
decorator from @angular/core
, like so:
HTML Code:
<form #f="ngForm">
...
...
</form>
TS Code:
import { ViewChild } from '@angular/core';
class TemplateFormComponent {
@ViewChild('f') myForm: any;
.
.
.
}
now you can use 'myForm' object to access any element within it in the class.
*/
import {Component,ViewChild} from '@angular/core' /*Import View Child*/
@Component({
selector:'display'
template:`
<input #myname (input) = "updateName(myname.value)"/>
<p> My name : {{myName}}</p>
`
})
export class DisplayComponent{
@ViewChild('myname')inputTxt:ElementRef; /*create a view child*/
myName: string;
updateName: Function;
constructor(){
this.myName = "Aman";
this.updateName = function(input: String){
this.inputTxt.nativeElement.value=this.myName;
/*assign to it the value*/
};
}
}
inputTxt
in that case.
I have used two way:
First way :
You can get a handle to the DOM element via ElementRef by injecting it into your component's constructor:
constructor(private myElement: ElementRef) {
this.myElement.nativeElement // <- your direct element reference
}
Second way:
@Component({
selector: 'my-app',
template:
`
<input #input value="enterThere">
`,
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
@ViewChild('input') input:ElementRef;
ngAfterViewInit() {
console.log(this.input);
}
Note: This doesn't apply to Angular 6 and above as ElementRef
became ElementRef<T>
with T
denoting the type of nativeElement
.
I would like to add that if you are using ElementRef
, as recommended by all answers, then you will immediately encounter the problem that ElementRef
has an awful type declaration that looks like
export declare class ElementRef {
nativeElement: any;
}
this is stupid in a browser environment where nativeElement is an HTMLElement
.
To workaround this you can use the following technique
import {Inject, ElementRef as ErrorProneElementRef} from '@angular/core';
interface ElementRef {
nativeElement: HTMLElement;
}
@Component({...}) export class MyComponent {
constructor(@Inject(ErrorProneElementRef) readonly elementRef: ElementRef) { }
}
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.
let item: ElementRef, item2: ElementRef; item = item2
fails because of definite assignment analysis. Your second fails for the same reasons but both succeed if item2
is initialized for the reasons discussed (or as a useful quick check for assignability we can use declare let
here). Regardless, truly a shame to see any
on a public API like this.
Mimimum example for quick usage:
import { Component, ElementRef, ViewChild} from '@angular/core';
@Component({
selector: 'my-app',
template:
`
<input #inputEl value="hithere">
`,
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
@ViewChild('inputEl') inputEl:ElementRef;
ngAfterViewInit() {
console.log(this.inputEl);
}
}
Put a template reference variable on the DOM element of interest. In our example this is the #inputEl on the tag. In our component class inject the DOM element via the @ViewChild decorator Access the element in the ngAfterViewInit lifecycle hook.
Note:
If you want to manipulate the DOM elements use the Renderer2 API instead of accessing the elements directly. Permitting direct access to the DOM can make your application more vulnerable to XSS attacks
to get the immediate next sibling ,use this
event.source._elementRef.nativeElement.nextElementSibling
Selecting target element from the list. It is easy to select particular element from the list of same elements.
component code:
export class AppComponent {
title = 'app';
listEvents = [
{'name':'item1', 'class': ''}, {'name':'item2', 'class': ''},
{'name':'item3', 'class': ''}, {'name':'item4', 'class': ''}
];
selectElement(item: string, value: number) {
console.log("item="+item+" value="+value);
if(this.listEvents[value].class == "") {
this.listEvents[value].class='selected';
} else {
this.listEvents[value].class= '';
}
}
}
html code:
<ul *ngFor="let event of listEvents; let i = index">
<li (click)="selectElement(event.name, i)" [class]="event.class">
{{ event.name }}
</li>
css code:
.selected {
color: red;
background:blue;
}
For components inside *ngIf
, another approach:
The component I wanted to select was inside a div's *ngIf statement, and @jsgoupil's answer above probably works (Thanks @jsgoupil!), but I ended up finding a way to avoid using *ngIf, by using CSS to hide the element.
When the condition in the [className] is true, the div gets displayed, and naming the component using # works and it can be selected from within the typescript code. When the condition is false, it's not displayed, and I don't need to select it anyway.
Component:
@Component({
selector: 'bla',
templateUrl: 'bla.component.html',
styleUrls: ['bla.component.scss']
})
export class BlaComponent implements OnInit, OnDestroy {
@ViewChild('myComponentWidget', {static: true}) public myComponentWidget: any;
@Input('action') action: ActionType; // an enum defined in our code. (action could also be declared locally)
constructor() {
etc;
}
// this lets you use an enum in the HMTL (ActionType.SomeType)
public get actionTypeEnum(): typeOf ActionType {
return ActionType;
}
public someMethodXYZ: void {
this.myComponentWidget.someMethod(); // use it like that, assuming the method exists
}
and then in the bla.component.html file:
<div [className]="action === actionTypeEnum.SomeType ? 'show-it' : 'do-not-show'">
<my-component #myComponentWidget etc></my-component>
</div>
<div>
<button type="reset" class="bunch-of-classes" (click)="someMethodXYZ()">
<span>XYZ</span>
</button>
</div>
and the CSS file:
::ng-deep {
.show-it {
display: block; // example, actually a lot more css in our code
}
.do-not-show {
display: none';
}
}
In case you are using Angular Material, you can take advantage of cdkFocusInitial directive.
Example: <input matInput cdkFocusInitial>
Read more here: https://material.angular.io/cdk/a11y/overview#regions
Success story sharing
input
also is anElementRef
, but you get the reference to the element you actually want, instead of querying it from the hostElementRef
.ElementRef
is just fine. Also usingElementRef.nativeElement
withRenderer
is fine. What is discouraged is accessing properties ofElementRef.nativeElement.xxx
directly.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).@ViewChild('myObj', { read: ElementRef }) myObj: ElementRef;