如何在组件模板中选择元素?


516

有人知道如何掌握组件模板中定义的元素吗?使用$和可以使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;
    }
}

如何在类定义中捕获或获取pinput元素的引用?

Answers:


936

代替从那里注入ElementRef和使用querySelector或类似方法,可以使用声明性方法直接访问视图中的元素:

<input #myname>
@ViewChild('myname') input; 

元件

ngAfterViewInit() {
  console.log(this.input.nativeElement.value);
}

StackBlitz示例

子孙

@ContentChildren() 是唯一允许查询后代的人

@ContentChildren(SomeTypeOrVarName, {descendants: true}) someField; 

{descendants: true}应该是默认值,但不在2.0.0最终版本中,并且被认为是错误。
错误已在2.0.1中修复

如果有组件和指令,则该read参数允许指定应返回哪个实例。

例如ViewContainerRef,动态创建的组件而不是默认组件所需要的ElementRef

@ViewChild('myname', { read: ViewContainerRef }) target;

订阅更改

即使仅在ngAfterViewInit()调用时设置了视图子级,而在调用时仅设置了内容子级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);
  }
}

获得任意的投影内容

请参阅访问包含的内容


12
角度小组建议不要使用ElementRef,这是更好的解决方案。
周星驰

7
实际上input也是一个ElementRef,但是您可以获取对实际所需元素的引用,而不是从host查询它ElementRef
君特Zöchbauer

32
实际使用ElementRef就可以了。也可以使用ElementRef.nativeElementwith Renderer。什么是气馁的访问的性能ElementRef.nativeElement.xxx直接。
君特Zöchbauer

2
@Natanael我不知道是否在此处明确记录此文件,但在应避免直接访问DOM的问题或其他讨论(也是来自Angular团队成员)中经常提到的内容。直接访问DOM(这是访问的属性和方法ElementRef.nativeElement)是,使您无法使用Angulars服务器端渲染和WebWorker功能(我不知道这是否也打破了即将到来的脱机模板编译器-但我想不会)。
冈特Zöchbauer16年

10
如上面在阅读部分中所述,如果要使用ViewChild获取元素的nativeElement,则必须执行以下操作: @ViewChild('myObj', { read: ElementRef }) myObj: ElementRef;
jsgoupil

203

您可以ElementRef通过将DOM元素注入到组件的构造函数中来获得DOM元素的句柄:

constructor(myElement: ElementRef) { ... }

文件:https//angular.io/docs/ts/latest/api/core/index/ElementRef-class.html


1
@Brocco您可以更新您的答案吗?我想看看当前的解决方案,因为ElementRef它已经消失了。
Jefftopia

23
ElementRef可用(再次?)。
君特Zöchbauer

10
链接当需要直接访问DOM时, 使用此API作为最后的手段。改用Angular提供的模板和数据绑定。另外,您可以看一下Renderer,它提供了即使不支持直接访问本机元素也可以安全使用的API。依靠直接DOM访问会在您的应用程序和呈现层之间建立紧密的耦合,这将使得不可能将两者分开并将您的应用程序部署到Web Worker中。
sandeep talabathula

@sandeeptalabathula有什么更好的选择来找到将来自第三方库的浮动日期选择器组件附加到的元素?我知道这不是最初的问题,但是您发现在所有情况下在DOM中查找元素都是不好的……
John

13
@john Ah ..好的。您可以尝试一下- this.element.nativeElement.querySelector('#someElementId')像这样将ElementRef传递给构造函数。 private element: ElementRef, 导入lib ...import { ElementRef } from '@angular/core';
sandeep talabathula

52
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) {
    // ...
  }
}

示例已更新以使用最新版本

有关本机元素的更多详细信息,请单击此处


20

Angular 4+renderer.selectRootElement与CSS选择器一起使用以访问元素。

我有一个最初显示电子邮件输入的表单。输入电子邮件后,该表单将被扩展以允许他们继续添加有关其项目的信息。但是,如果他们不是现有客户,则该表单将在项目信息部分上方包含一个地址部分。

到目前为止,数据输入部分尚未拆分为组件,因此使用* ngIf指令管理这些部分。如果它们是现有客户,则需要将重点放在“项目注释”字段上,如果是新客户,则需要将其放在“名字”字段上。

我尝试解决方案没有成功。但是,答案中的Update 3 给了我一半的最终解决方案。另一半来自MatteoNY在主题中的回应。结果是这样的:

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.selectRootElementAngular之外。因为我需要给新的部分渲染时间,所以将元素部分包裹在一个超时中,以允许渲染线程有时间在尝试选择元素之前赶上。完成所有设置后,我可以使用基本的CSS选择器简单地调用该元素。

我知道这个示例主要处理焦点事件,但是对我来说很难在其他情况下使用。


从Angular 4.3.0开始,类Renderer被弃用。angular.io/api/core/Renderer
Jamie

15

对于尝试在*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 { ... }

使用此模板获取ElementRefAND 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) {
}

12

ViewChild从导入装饰器@angular/core,如下所示:

HTML代码:

<form #f="ngForm"> 
  ... 
  ... 
</form>

TS代码:

import { ViewChild } from '@angular/core';

class TemplateFormComponent {

  @ViewChild('f') myForm: any;
    .
    .
    .
}

现在,您可以使用“ myForm”对象来访问类中的任何元素。

Source


但是您应该注意到,您几乎不需要访问组件类中的模板元素,只需要正确理解角度逻辑即可。
汉尼(Hany),2017年

3
不要使用任何类型,类型是ElementRef
Johannes

10
 */
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*/
        };
    }
}

10
请对此代码提供一些说明。强烈建议不要进行简单的代码转储而无需解释。
rayryeng

5
这将行不通:通过@ViewChild批注设置的属性仅在ngAfterViewInit生命周期事件之后可用。inputTxt在这种情况下,访问构造函数中的值将产生未定义的值。
David M.

使用ang 7,可以完美地工作。
MizAkita

5

:这并不适用于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) { }
}

1
这说明了我遇到的一个问题。这是行不通的,因为item即使您将其设置为另一个ElementRef:,它也会说需要是一个ElementRef let item:ElementRef, item2:ElementRef; item = item2; // no can do.。非常混乱。但这很好: let item:ElementRef, item2:ElementRef; item = item2.nativeElement因为您指出了实现方式。
oooyaya'3

1
实际上,您的第一个示例let item: ElementRef, item2: ElementRef; item = item2由于进行明确的赋值分析而失败。您的第二个失败是出于相同的原因,但是如果item2由于所讨论的原因而被初始化(或者作为可declare let在此处使用的有用的快速检查性的有用检查)而被初始化,则第二个都会失败。无论如何,any在这样的公共API上看到确实可惜。
Aluan Haddad

2

获取下一个兄弟姐妹,使用此

event.source._elementRef.nativeElement.nextElementSibling

1

从列表中选择目标元素。从相同元素的列表中选择特定元素很容易。

组件代码:

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

0

快速使用的最小示例:

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);
  }
}
  1. 将模板引用变量放在感兴趣的DOM元素上。在我们的示例中,这是标签#inputEl上的<input>
  2. 在我们的组件类中,通过@ViewChild装饰器注入DOM元素
  3. 访问ngAfterViewInit生命周期挂钩中的元素。

注意:

如果要操作DOM元素,请使用Renderer2 API,而不是直接访问元素。允许直接访问DOM可以使您的应用程序更容易受到XSS攻击

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.