如何 select 组件模板中的元素?
How can I select an element in a component template?
有人知道如何获取组件模板中定义的元素吗? Polymer 使 $
和 $$
.
变得非常简单
我只是想知道如何在 Angular 中进行。
以教程为例:
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;
}
}
如何从 class 定义中捕获或获取 p
或 input
元素的引用?
您可以通过 ElementRef
将 DOM 元素的句柄注入到组件的构造函数中:
constructor(private myElement: ElementRef) { ... }
文档:https://angular.io/docs/ts/latest/api/core/index/ElementRef-class.html
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) {
// ...
}
}
更新示例以使用最新版本
有关原生元素的更多详细信息,here
不是注入 ElementRef
并从那里使用 querySelector
或类似的方法,而是可以使用声明方式直接访问视图中的元素:
<input #myname>
@ViewChild('myname') input;
元素
ngAfterViewInit() {
console.log(this.input.nativeElement.value);
}
- @ViewChild() 支持指令或组件类型作为参数,或模板变量的名称(字符串)。
- @ViewChildren() 还支持以逗号分隔的名称列表(目前不允许使用空格
@ViewChildren('var1,var2,var3')
)。
- @ContentChild() and @ContentChildren() 做同样的事情,但在光线下 DOM(
<ng-content>
投影元素)。
后裔
@ContentChildren()
是唯一一个允许查询后代的
@ContentChildren(SomeTypeOrVarName, {descendants: true}) someField;
{descendants: true}
应该是默认值,但不是 2.0.0 final 而是 considered a bug
这已在 2.0.1
中修复
阅读
如果有组件和指令,read
参数允许指定应返回哪个实例。
例如动态创建的组件需要 ViewContainerRef
而不是默认的 ElementRef
@ViewChild('myname', { read: ViewContainerRef }) target;
订阅更改
虽然view children只在调用ngAfterViewInit()
时设置,content children只在调用ngAfterContentInit()
时设置,如果你想订阅查询结果的变化,应该是在 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));
}
直接DOM访问
只能查询 DOM 个元素,不能查询组件或指令实例:
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);
}
}
获取任意投影内容
见
对于试图在 *ngIf
或 *ngSwitchCase
中获取组件实例的人,您可以遵循这个技巧。
创建一个 init
指令。
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);
}
}
使用 myComponent
等名称导出您的组件
@Component({
selector: 'wm-my-component',
templateUrl: 'my-component.component.html',
styleUrls: ['my-component.component.css'],
exportAs: 'myComponent'
})
export class MyComponent { ... }
使用此模板获取 ElementRef
AND MyComponent
实例
<div [ngSwitch]="type">
<wm-my-component
#myComponent="myComponent"
*ngSwitchCase="Type.MyType"
(init)="init($event, myComponent)">
</wm-my-component>
</div>
在 TypeScript 中使用此代码
init(myComponentRef: ElementRef, myComponent: MyComponent) {
}
*/
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*/
};
}
}
注意:这不适用于 Angular 6 及更高版本,因为 ElementRef
变成了 ElementRef<T>
和 T
表示 nativeElement
的类型。
我想补充一点,如果您按照所有答案的建议使用 ElementRef
,那么您会立即遇到这样的问题,即 ElementRef
有一个糟糕的类型声明,看起来像
export declare class ElementRef {
nativeElement: any;
}
这在 nativeElement 是 HTMLElement
.
的浏览器环境中是愚蠢的
要解决此问题,您可以使用以下技术
import {Inject, ElementRef as ErrorProneElementRef} from '@angular/core';
interface ElementRef {
nativeElement: HTMLElement;
}
@Component({...}) export class MyComponent {
constructor(@Inject(ErrorProneElementRef) readonly elementRef: ElementRef) { }
}
Angular 4+:
使用 renderer.selectRootElement
和 CSS 选择器来访问元素。
我有一个最初显示电子邮件输入的表单。输入电子邮件后,表格将展开以允许他们继续添加与其项目相关的信息。但是,如果他们不是现有客户,表格将在项目信息部分上方包含一个地址部分。
到目前为止,数据输入部分还没有分解成组件,所以这些部分是用 *ngIf 指令管理的。如果他们是现有客户,我需要关注项目备注字段,如果他们是新客户,我需要关注名字字段。
我尝试了这些解决方案但没有成功。但是, answer gave me half of the eventual solution. The other half came from MatteoNY's response in this 线程中的更新 3。结果是这样的:
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');
}
}
因为我唯一要做的就是将焦点设置在一个元素上,所以我不需要关心变化检测,所以我实际上可以 运行 调用 renderer.selectRootElement
Angular 之外。因为我需要给新的部分时间来渲染,元素部分被包裹在一个超时中,让渲染线程有时间在尝试元素选择之前赶上。完成所有设置后,我可以使用基本 CSS 选择器简单地调用元素。
我知道这个示例主要处理焦点事件,但我很难将其用于其他上下文。
更新:Angular 在 Angular 4 中放弃了对 Renderer
的支持,并在 Angular 9 中完全删除了它。解决方案不应受到迁移到 Renderer2
的影响。请参阅此 link 以获取更多信息:
Renderer migration to Renderer2
要获取直接的下一个兄弟姐妹,请使用此
event.source._elementRef.nativeElement.nextElementSibling
从 @angular/core
导入 ViewChild
装饰器,像这样:
HTML代码:
<form #f="ngForm">
...
...
</form>
TS代码:
import { ViewChild } from '@angular/core';
class TemplateFormComponent {
@ViewChild('f') myForm: any;
.
.
.
}
现在您可以使用 'myForm' 对象访问 class 中的任何元素。
正在从列表中选择目标元素。从相同元素列表中 select 特定元素很容易。
组件代码:
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代码:
<ul *ngFor="let event of listEvents; let i = index">
<li (click)="selectElement(event.name, i)" [class]="event.class">
{{ event.name }}
</li>
css代码:
.selected {
color: red;
background:blue;
}
快速使用的最小示例:
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);
}
}
- 将模板引用变量放在感兴趣的 DOM 元素上。在我们的示例中,这是
<input>
标签上的 #inputEl
。
- 在我们的组件中 class 通过 @ViewChild 装饰器注入 DOM 元素
- 访问
ngAfterViewInit
生命周期挂钩中的元素。
注:
如果要操作 DOM 元素,请使用 Renderer2 API 而不是直接访问元素。允许直接访问 DOM 会使您的应用程序更容易受到 XSS 攻击
如果您正在使用 Angular Material,您可以利用 cdkFocusInitial 指令。
示例:<input matInput cdkFocusInitial>
在此处阅读更多内容:
https://material.angular.io/cdk/a11y/overview#regions
对于*ngIf
内的组件,另一种方法:
我想要 select 的组件在 div 的 *ngIf 语句中,@jsgoupil 上面的回答可能有效(感谢@jsgoupil!),但我最终找到了一种方法为了避免使用 *ngIf,通过使用 CSS 隐藏元素。
当 [className] 中的条件为真时,将显示 div,并使用 # 命名组件,并且可以从 typescript 代码中 selected。条件为false时不显示,反正我也不需要select
分量:
@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
}
然后在 bla.component.html 文件中:
<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>
和 CSS 文件:
::ng-deep {
.show-it {
display: block; // example, actually a lot more css in our code
}
.do-not-show {
display: none';
}
}
我用过两种方式:
第一种方式:
您可以通过将 ElementRef 注入组件的构造函数来获取 DOM 元素的句柄:
constructor(private myElement: ElementRef) {
this.myElement.nativeElement // <- your direct element reference
}
第二种方式:
@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);
}
有人知道如何获取组件模板中定义的元素吗? Polymer 使 $
和 $$
.
我只是想知道如何在 Angular 中进行。
以教程为例:
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;
}
}
如何从 class 定义中捕获或获取 p
或 input
元素的引用?
您可以通过 ElementRef
将 DOM 元素的句柄注入到组件的构造函数中:
constructor(private myElement: ElementRef) { ... }
文档:https://angular.io/docs/ts/latest/api/core/index/ElementRef-class.html
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) {
// ...
}
}
更新示例以使用最新版本
有关原生元素的更多详细信息,here
不是注入 ElementRef
并从那里使用 querySelector
或类似的方法,而是可以使用声明方式直接访问视图中的元素:
<input #myname>
@ViewChild('myname') input;
元素
ngAfterViewInit() {
console.log(this.input.nativeElement.value);
}
- @ViewChild() 支持指令或组件类型作为参数,或模板变量的名称(字符串)。
- @ViewChildren() 还支持以逗号分隔的名称列表(目前不允许使用空格
@ViewChildren('var1,var2,var3')
)。 - @ContentChild() and @ContentChildren() 做同样的事情,但在光线下 DOM(
<ng-content>
投影元素)。
后裔
@ContentChildren()
是唯一一个允许查询后代的
@ContentChildren(SomeTypeOrVarName, {descendants: true}) someField;
{descendants: true}
应该是默认值,但不是 2.0.0 final 而是 considered a bug
这已在 2.0.1
阅读
如果有组件和指令,read
参数允许指定应返回哪个实例。
例如动态创建的组件需要 ViewContainerRef
而不是默认的 ElementRef
@ViewChild('myname', { read: ViewContainerRef }) target;
订阅更改
虽然view children只在调用ngAfterViewInit()
时设置,content children只在调用ngAfterContentInit()
时设置,如果你想订阅查询结果的变化,应该是在 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));
}
直接DOM访问
只能查询 DOM 个元素,不能查询组件或指令实例:
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);
}
}
获取任意投影内容
见
对于试图在 *ngIf
或 *ngSwitchCase
中获取组件实例的人,您可以遵循这个技巧。
创建一个 init
指令。
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);
}
}
使用 myComponent
@Component({
selector: 'wm-my-component',
templateUrl: 'my-component.component.html',
styleUrls: ['my-component.component.css'],
exportAs: 'myComponent'
})
export class MyComponent { ... }
使用此模板获取 ElementRef
AND MyComponent
实例
<div [ngSwitch]="type">
<wm-my-component
#myComponent="myComponent"
*ngSwitchCase="Type.MyType"
(init)="init($event, myComponent)">
</wm-my-component>
</div>
在 TypeScript 中使用此代码
init(myComponentRef: ElementRef, myComponent: MyComponent) {
}
*/
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*/
};
}
}
注意:这不适用于 Angular 6 及更高版本,因为 ElementRef
变成了 ElementRef<T>
和 T
表示 nativeElement
的类型。
我想补充一点,如果您按照所有答案的建议使用 ElementRef
,那么您会立即遇到这样的问题,即 ElementRef
有一个糟糕的类型声明,看起来像
export declare class ElementRef {
nativeElement: any;
}
这在 nativeElement 是 HTMLElement
.
要解决此问题,您可以使用以下技术
import {Inject, ElementRef as ErrorProneElementRef} from '@angular/core';
interface ElementRef {
nativeElement: HTMLElement;
}
@Component({...}) export class MyComponent {
constructor(@Inject(ErrorProneElementRef) readonly elementRef: ElementRef) { }
}
Angular 4+:
使用 renderer.selectRootElement
和 CSS 选择器来访问元素。
我有一个最初显示电子邮件输入的表单。输入电子邮件后,表格将展开以允许他们继续添加与其项目相关的信息。但是,如果他们不是现有客户,表格将在项目信息部分上方包含一个地址部分。
到目前为止,数据输入部分还没有分解成组件,所以这些部分是用 *ngIf 指令管理的。如果他们是现有客户,我需要关注项目备注字段,如果他们是新客户,我需要关注名字字段。
我尝试了这些解决方案但没有成功。但是,
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');
}
}
因为我唯一要做的就是将焦点设置在一个元素上,所以我不需要关心变化检测,所以我实际上可以 运行 调用 renderer.selectRootElement
Angular 之外。因为我需要给新的部分时间来渲染,元素部分被包裹在一个超时中,让渲染线程有时间在尝试元素选择之前赶上。完成所有设置后,我可以使用基本 CSS 选择器简单地调用元素。
我知道这个示例主要处理焦点事件,但我很难将其用于其他上下文。
更新:Angular 在 Angular 4 中放弃了对 Renderer
的支持,并在 Angular 9 中完全删除了它。解决方案不应受到迁移到 Renderer2
的影响。请参阅此 link 以获取更多信息:
Renderer migration to Renderer2
要获取直接的下一个兄弟姐妹,请使用此
event.source._elementRef.nativeElement.nextElementSibling
从 @angular/core
导入 ViewChild
装饰器,像这样:
HTML代码:
<form #f="ngForm">
...
...
</form>
TS代码:
import { ViewChild } from '@angular/core';
class TemplateFormComponent {
@ViewChild('f') myForm: any;
.
.
.
}
现在您可以使用 'myForm' 对象访问 class 中的任何元素。
正在从列表中选择目标元素。从相同元素列表中 select 特定元素很容易。
组件代码:
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代码:
<ul *ngFor="let event of listEvents; let i = index">
<li (click)="selectElement(event.name, i)" [class]="event.class">
{{ event.name }}
</li>
css代码:
.selected {
color: red;
background:blue;
}
快速使用的最小示例:
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);
}
}
- 将模板引用变量放在感兴趣的 DOM 元素上。在我们的示例中,这是
<input>
标签上的#inputEl
。 - 在我们的组件中 class 通过 @ViewChild 装饰器注入 DOM 元素
- 访问
ngAfterViewInit
生命周期挂钩中的元素。
注:
如果要操作 DOM 元素,请使用 Renderer2 API 而不是直接访问元素。允许直接访问 DOM 会使您的应用程序更容易受到 XSS 攻击
如果您正在使用 Angular Material,您可以利用 cdkFocusInitial 指令。
示例:<input matInput cdkFocusInitial>
在此处阅读更多内容: https://material.angular.io/cdk/a11y/overview#regions
对于*ngIf
内的组件,另一种方法:
我想要 select 的组件在 div 的 *ngIf 语句中,@jsgoupil 上面的回答可能有效(感谢@jsgoupil!),但我最终找到了一种方法为了避免使用 *ngIf,通过使用 CSS 隐藏元素。
当 [className] 中的条件为真时,将显示 div,并使用 # 命名组件,并且可以从 typescript 代码中 selected。条件为false时不显示,反正我也不需要select
分量:
@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
}
然后在 bla.component.html 文件中:
<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>
和 CSS 文件:
::ng-deep {
.show-it {
display: block; // example, actually a lot more css in our code
}
.do-not-show {
display: none';
}
}
我用过两种方式:
第一种方式:
您可以通过将 ElementRef 注入组件的构造函数来获取 DOM 元素的句柄:
constructor(private myElement: ElementRef) {
this.myElement.nativeElement // <- your direct element reference
}
第二种方式:
@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);
}