¿Cómo puedo seleccionar un elemento en una plantilla de componente?

517

¿Alguien sabe cómo hacerse con un elemento definido en una plantilla de componente? Polymer lo hace realmente fácil con $y $$.

Me preguntaba cómo hacerlo en Angular.

Tome el ejemplo del 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;
    }
}

¿Cómo atrapo u obtengo una referencia del elemento po inputdesde la definición de clase?

Aman Gupta
fuente

Respuestas:

939

En lugar de inyectar ElementRefy usar querySelectoro similar desde allí, se puede usar una forma declarativa para acceder directamente a los elementos en la vista:

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

elemento

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

Ejemplo de StackBlitz

  • @ViewChild () admite el tipo de directiva o componente como parámetro, o el nombre (cadena) de una variable de plantilla.
  • @ViewChildren () también admite una lista de nombres como lista separada por comas (actualmente no se permiten espacios @ViewChildren('var1,var2,var3')).
  • @ContentChild () y @ContentChildren () hacen lo mismo pero a la luz DOM ( <ng-content>elementos proyectados).

descendientes

@ContentChildren() es el único que permite consultar también descendientes

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

{descendants: true}debería ser el valor predeterminado pero no está en la versión 2.0.0 final y se considera un error
Esto se solucionó en 2.0.1

leer

Si hay un componente y directivas, el readparámetro permite especificar qué instancia debe devolverse.

Por ejemplo, ViewContainerRefeso es requerido por los componentes creados dinámicamente en lugar del predeterminadoElementRef

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

suscribir cambios

Aunque los elementos secundarios de vista solo se configuran cuando ngAfterViewInit()se llama y los elementos secundarios de contenido solo se configuran cuando ngAfterContentInit()se llama, si desea suscribirse a los cambios del resultado de la consulta, debe hacerlo enngOnInit()

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

acceso DOM directo

solo puede consultar elementos DOM, pero no componentes o instancias de directivas:

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

obtener contenido proyectado arbitrario

Ver Acceso a contenido transcluido

Günter Zöchbauer
fuente
12
Los equipos angulares desaconsejaron el uso de ElementRef, esta es la mejor solución.
Honorable Chow
77
En realidad inputtambién es un ElementRef, pero obtienes la referencia al elemento que realmente deseas, en lugar de consultarlo desde el host ElementRef.
Günter Zöchbauer
32
En realidad, usar ElementRefestá bien. También usar ElementRef.nativeElementcon Rendererestá bien. Lo que se desaconseja es acceder a las propiedades de ElementRef.nativeElement.xxxdirectamente.
Günter Zöchbauer
2
@Natanael No sé si esto está documentado explícitamente o dónde, pero se menciona regularmente en problemas u otras discusiones (también de los miembros del equipo angular) que se debe evitar el acceso DOM directo. Acceso a la DOM directamente (que es lo que acceder a las propiedades y métodos de ElementRef.nativeElement)decir, no le permite usar angulares representación del lado del servidor y función WebWorker (no sé si también se rompe la próxima plantilla compilador en línea - pero supongo que no).
Günter Zöchbauer
10
Como se mencionó anteriormente en la sección de lectura , si desea obtener el elemento nativo para un elemento con ViewChild, debe hacer lo siguiente: @ViewChild('myObj', { read: ElementRef }) myObj: ElementRef;
jsgoupil
203

Puede obtener un identificador para el elemento DOM mediante la ElementRefinyección en el constructor de su componente:

constructor(myElement: ElementRef) { ... }

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

Brocco
fuente
1
@Brocco, ¿puedes actualizar tu respuesta? Me gustaría ver una solución actual ya que ElementRefse ha ido.
Jefftopia
23
ElementRefestá disponible (¿otra vez?)
Günter Zöchbauer
10
enlace Use esta API como último recurso cuando se necesita acceso directo a DOM. Use plantillas y enlaces de datos proporcionados por Angular en su lugar. Alternativamente, eche un vistazo a Renderer, que proporciona API que se puede usar de forma segura incluso cuando no se admite el acceso directo a elementos nativos. Confiar en el acceso DOM directo crea un acoplamiento estrecho entre su aplicación y las capas de representación, lo que hará que sea imposible separarlas e implementar su aplicación en un trabajador web.
sandeep talabathula
@sandeeptalabathula ¿Cuál es una mejor opción para encontrar un elemento para adjuntar un componente selector de fecha flotante de una biblioteca de terceros? Soy consciente de que esta no era la pregunta original, pero entiendes que encontrar elementos en el DOM es malo en todos los escenarios ...
John
13
@ John Ah ... está bien. Puede probar esto, this.element.nativeElement.querySelector('#someElementId')y pasar ElementRef al constructor así ... private element: ElementRef, Importar 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) {
    // ...
  }
}

Ejemplo actualizado para trabajar con la última versión

Para más detalles sobre el elemento nativo, aquí

gdi2290
fuente
20

Angular 4+ : se usa renderer.selectRootElementcon un selector CSS para acceder al elemento.

Tengo un formulario que inicialmente muestra una entrada de correo electrónico. Después de ingresar el correo electrónico, el formulario se ampliará para permitirles continuar agregando información relacionada con su proyecto. Sin embargo, si son no un cliente existente, el formulario incluye una sección de dirección por encima de la sección de información del proyecto.

A partir de ahora, la parte de entrada de datos no se ha dividido en componentes, por lo que las secciones se administran con directivas * ngIf. Necesito poner el foco en el campo de notas del proyecto si son clientes existentes, o en el campo de nombre si son nuevos.

Probé las soluciones sin éxito. Sin embargo, la Actualización 3 en esta respuesta me dio la mitad de la solución final. La otra mitad provino de la respuesta de MatteoNY en este hilo. El resultado es este:

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

Como lo único que estoy haciendo es enfocarme en un elemento, no necesito preocuparme por la detección de cambios, por lo que puedo ejecutar la llamada desde renderer.selectRootElementfuera de Angular. Debido a que necesito dar tiempo a las nuevas secciones para que se procesen, la sección del elemento se ajusta en un tiempo de espera para permitir que los hilos de representación se pongan al día antes de que se intente la selección del elemento. Una vez que todo está configurado, simplemente puedo llamar al elemento usando selectores básicos de CSS.

Sé que este ejemplo se ocupó principalmente del evento de enfoque, pero es difícil para mí que esto no pueda usarse en otros contextos.

Neil T.
fuente
La clase Renderer está DEPRECADA desde Angular 4.3.0. angular.io/api/core/Renderer
Jamie
15

Para las personas que intentan tomar la instancia del componente dentro de un *ngIfo *ngSwitchCase, puedes seguir este truco.

Crea una initdirectiva.

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

Exporte su componente con un nombre como myComponent

@Component({
    selector: 'wm-my-component',
    templateUrl: 'my-component.component.html',
    styleUrls: ['my-component.component.css'],
    exportAs: 'myComponent'
})
export class MyComponent { ... }

Use esta plantilla para obtener la instancia ElementRefANDMyComponent

<div [ngSwitch]="type">
    <wm-my-component
           #myComponent="myComponent"
           *ngSwitchCase="Type.MyType"
           (init)="init($event, myComponent)">
    </wm-my-component>
</div>

Use este código en TypeScript

init(myComponentRef: ElementRef, myComponent: MyComponent) {
}
jsgoupil
fuente
12

importar el ViewChilddecorador desde@angular/core , así:

Código HTML:

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

Código TS:

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

class TemplateFormComponent {

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

ahora puede usar el objeto 'myForm' para acceder a cualquier elemento dentro de él en la clase.

Source

Hany
fuente
Pero debe notar que casi no necesita acceder a elementos de plantilla en la clase de componente, solo necesita comprender bien la lógica angular.
Hany
3
No use ninguno, el tipo es 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*/
        };
    }
}
Ing. Gabr
fuente
10
Proporcione alguna explicación a este código. Simplemente se desaconseja el vertido de código sin explicación.
rayryeng
55
Esto no funcionará: los atributos establecidos a través de las anotaciones @ViewChild solo estarán disponibles después del evento ngAfterViewInit lifecycle. Acceder al valor en el constructor produciría un valor indefinido para inputTxten ese caso.
David M.
Usando ang 7, esto funcionó a la perfección.
MizAkita
5

Nota : Esto no se aplica a Angular 6 y superior como seElementRefhizoElementRef<T>alTdenotar el tipo denativeElement.

Me gustaría agregar que si está utilizando ElementRef, como lo recomiendan todas las respuestas, inmediatamente se encontrará con el problema que ElementReftiene una declaración de tipo horrible que se parece

export declare class ElementRef {
  nativeElement: any;
}

Esto es estúpido en un entorno de navegador donde nativeElement es un HTMLElement.

Para solucionar esto, puede usar la siguiente técnica

import {Inject, ElementRef as ErrorProneElementRef} from '@angular/core';

interface ElementRef {
  nativeElement: HTMLElement;
}

@Component({...}) export class MyComponent {
  constructor(@Inject(ErrorProneElementRef) readonly elementRef: ElementRef) { }
}
Aluan Haddad
fuente
1
Esto explica un problema que estaba teniendo. Esto no funciona, ya que va a decir itemtiene que ser un RefElement, a pesar de que se está configurando a otro RefElement: let item:ElementRef, item2:ElementRef; item = item2; // no can do.. Muy confuso. Pero esto está bien: let item:ElementRef, item2:ElementRef; item = item2.nativeElementdebido a la implementación que señaló.
oooyaya
1
En realidad, su primer ejemplo let item: ElementRef, item2: ElementRef; item = item2falla debido al análisis de asignación definitiva. Su segundo falla por los mismos motivos, pero ambos tienen éxito si item2se inicializa por los motivos discutidos (o como una comprobación rápida útil de asignabilidad que podemos usar declare letaquí). De todos modos, es realmente una pena ver anyen una API pública como esta.
Aluan Haddad
2

para obtener el próximo hermano inmediato, use esto

event.source._elementRef.nativeElement.nextElementSibling
Apoorv
fuente
1

Selección del elemento objetivo de la lista. Es fácil seleccionar un elemento particular de la lista de los mismos elementos.

código de componente:

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

código HTML:

<ul *ngFor="let event of listEvents; let i = index">
   <li  (click)="selectElement(event.name, i)" [class]="event.class">
  {{ event.name }}
</li>

código css:

.selected {
  color: red;
  background:blue;
}
Sai Goud
fuente
0

Ejemplo mínimo para un uso rápido:

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. Coloque una variable de referencia de plantilla en el elemento DOM de interés. En nuestro ejemplo, este es el #inputElde la <input>etiqueta.
  2. En nuestra clase de componente, inyecte el elemento DOM a través del decorador @ViewChild
  3. Acceda al elemento en el ngAfterViewInitgancho del ciclo de vida.

Nota:

Si desea manipular los elementos DOM, use la API Renderer2 en lugar de acceder a los elementos directamente. Permitir el acceso directo al DOM puede hacer que su aplicación sea más vulnerable a los ataques XSS

Willem van der Veen
fuente