Como posso selecionar um elemento em um modelo de componente?


516

Alguém sabe como se apossar de um elemento definido em um modelo de componente? O polímero facilita muito com o$ e $$.

Eu só estava me perguntando como fazer isso no Angular.

Veja o exemplo do 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;
    }
}

Como reter ou obter uma referência do elemento pou inputna definição de classe?

Respostas:


938

Em vez de injetar ElementRefe usar querySelectorou similar a partir daí, uma maneira declarativa pode ser usada para acessar elementos na visualização diretamente:

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

elemento

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

Exemplo de StackBlitz

  • @ViewChild () suporta diretiva ou tipo de componente como parâmetro ou o nome (string) de uma variável de modelo.
  • O @ViewChildren () também suporta uma lista de nomes como lista separada por vírgula (atualmente não são permitidos espaços @ViewChildren('var1,var2,var3')).
  • @ContentChild () e @ContentChildren () fazem o mesmo, mas na luz DOM ( <ng-content>elementos projetados).

descendentes

@ContentChildren() é o único que permite também consultar descendentes

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

{descendants: true}deve ser o padrão, mas não está na versão 2.0.0 final e é considerado um bug
Isso foi corrigido na versão 2.0.1

ler

Se houver um componente e diretrizes, o read parâmetro permite especificar qual instância deve ser retornada.

Por exemplo, ViewContainerRefexigido pelos componentes criados dinamicamente, em vez do padrãoElementRef

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

assinar alterações

Mesmo que os filhos de exibição sejam definidos apenas quando ngAfterViewInit()chamados e os filhos de conteúdo somente quando ngAfterContentInit()chamados, se você deseja assinar alterações do resultado da consulta, isso deve ser feito emngOnInit()

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

acesso direto ao DOM

pode consultar apenas elementos DOM, mas não componentes ou instâncias de diretiva:

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

obtenha conteúdo projetado arbitrário

Consulte Acessar conteúdo transcluído


12
As equipes angulares desaconselharam o uso do ElementRef, esta é a melhor solução.
Honorável Chow

7
Na verdade, inputtambém é um ElementRef, mas você obtém a referência ao elemento que realmente deseja, em vez de consultá-lo no host ElementRef.
Günter Zöchbauer 30/03

32
Na verdade, usar ElementRefestá bem. Também usando ElementRef.nativeElementcom Rendererestá bem. O que é desencorajado é acessar ElementRef.nativeElement.xxxdiretamente as propriedades .
Günter Zöchbauer

2
@Natanael Não sei se ou onde isso está explicitamente documentado, mas é mencionado regularmente em questões ou outras discussões (também de membros da equipe Angular) que o acesso direto ao DOM deve ser evitado. O acesso direto ao DOM (que é o que é o acesso às propriedades e métodos ElementRef.nativeElement), impede que você use a renderização no lado do servidor Angulars e o recurso WebWorker (não sei se ele também quebra o próximo compilador de modelos offline - mas acho que não).
Günter Zöchbauer

10
Como mencionado acima na leitura seção, se você deseja obter o nativeElement para um elemento com ViewChild, você tem que fazer o seguinte: @ViewChild('myObj', { read: ElementRef }) myObj: ElementRef;
jsgoupil

203

Você pode obter um identificador para o elemento DOM ElementRef, injetando-o no construtor do seu componente:

constructor(myElement: ElementRef) { ... }

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


1
@Brocco, você pode atualizar sua resposta? Eu gostaria de ver uma solução atual, uma vez que ElementRefse foi.
Jefftopia

23
ElementRefestá disponível (de novo?).
Günter Zöchbauer 04/02

10
link Use esta API como último recurso quando for necessário acesso direto ao DOM. Use modelos e ligação de dados fornecidos pelo Angular. Como alternativa, dê uma olhada no Renderer, que fornece API que pode ser usada com segurança, mesmo quando o acesso direto a elementos nativos não é suportado. Confiar no acesso DOM direto cria um forte acoplamento entre o aplicativo e as camadas de renderização, o que tornará impossível separar os dois e implantar o aplicativo em um trabalhador da Web.
sandeep talabathula

@sandeeptalabathula Qual é a melhor opção para encontrar um elemento ao qual anexar um componente selecionador de data flutuante de uma biblioteca de terceiros? Estou ciente de que essa não era a pergunta original, mas você descobre que encontrar elementos no DOM é ruim em todos os cenários ... #
John

13
@ john Ah .. tudo bem. Você pode tentar isso - this.element.nativeElement.querySelector('#someElementId')e passar o ElementRef para o construtor como este .. private element: ElementRef, Import 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) {
    // ...
  }
}

Exemplo atualizado para funcionar com a versão mais recente

Para mais detalhes sobre o elemento nativo, aqui


20

Angular 4+ : use renderer.selectRootElementcom um seletor de CSS para acessar o elemento.

Eu tenho um formulário que inicialmente exibe uma entrada de email. Após a entrada do email, o formulário será expandido para permitir que continuem adicionando informações relacionadas ao seu projeto. No entanto, se eles não forem um cliente existente, o formulário incluirá uma seção de endereço acima da seção de informações do projeto.

A partir de agora, a parte de entrada de dados não foi dividida em componentes; portanto, as seções são gerenciadas com as diretivas * ngIf. Preciso focar no campo de anotações do projeto, se ele for um cliente existente, ou no campo de nome, se for novo.

Eu tentei as soluções sem sucesso. No entanto, a atualização 3 nesta resposta me deu metade da solução eventual. A outra metade veio da resposta de MatteoNY neste tópico. O resultado é 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 a única coisa que estou fazendo é definir o foco em um elemento, não preciso me preocupar com a detecção de alterações, para poder executar a chamada para renderer.selectRootElement fora do Angular. Como preciso dar tempo para as novas seções renderizarem, a seção do elemento é encapsulada em um tempo limite para permitir que os segmentos de renderização recuperem o tempo antes que a seleção do elemento seja tentada. Depois que tudo estiver configurado, posso simplesmente chamar o elemento usando seletores CSS básicos.

Sei que este exemplo lidou principalmente com o evento de foco, mas é difícil para mim que isso não possa ser usado em outros contextos.


A classe Renderer é DEPRECATED desde Angular 4.3.0. angular.io/api/core/Renderer #
Jamie

15

Para pessoas que tentam capturar a instância do componente dentro de um *ngIfou*ngSwitchCase , você pode seguir este truque.

Crie uma initdiretiva.

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 seu componente com um nome como myComponent

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

Use este modelo para obter a instância ElementRefANDMyComponent

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

Use esse código no TypeScript

init(myComponentRef: ElementRef, myComponent: MyComponent) {
}

12

importar o ViewChilddecorador de@angular/core :

Código HTML:

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

Código TS:

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

class TemplateFormComponent {

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

agora você pode usar o objeto 'myForm' para acessar qualquer elemento dentro da classe.

Source


Mas você deve notar que quase não precisa acessar elementos de modelo na classe de componentes, apenas precisa entender bem a lógica angular corretamente.
Hany

3
Não use nenhum, o tipo é 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
Por favor, forneça algumas explicações para este código. Simplesmente despejar código sem explicação é altamente desencorajado.
rayryeng

5
Isso não funcionará: os atributos definidos por meio das anotações @ViewChild estarão disponíveis apenas após o evento do ciclo de vida ngAfterViewInit. Acessar o valor no construtor produziria um valor indefinido para inputTxtnesse caso.
David M.

Usando ang 7, isso funcionou perfeitamente.
MizAkita

5

Nota : Isso não se aplica ao Angular 6 e acima, comoElementRefaconteceuElementRef<T>com aTindicação do tipo denativeElement .

Gostaria de acrescentar que, se você estiver usando ElementRef, conforme recomendado por todas as respostas, encontrará imediatamente o problema que ElementRefpossui uma declaração de tipo terrível que se parece com

export declare class ElementRef {
  nativeElement: any;
}

isso é estúpido em um ambiente de navegador em que nativeElement é um HTMLElement .

Para solucionar isso, você pode usar a seguinte técnica

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

interface ElementRef {
  nativeElement: HTMLElement;
}

@Component({...}) export class MyComponent {
  constructor(@Inject(ErrorProneElementRef) readonly elementRef: ElementRef) { }
}

1
Isso explica um problema que eu estava tendo. Isso não funciona porque ele vai dizer itemnecessidades a ser um refElement, mesmo que você está definindo-o para outro refElement: let item:ElementRef, item2:ElementRef; item = item2; // no can do.. Muito confuso. Mas isso é bom: let item:ElementRef, item2:ElementRef; item = item2.nativeElementpor causa da implementação que você apontou.
oooyaya

1
Na verdade, seu primeiro exemplo let item: ElementRef, item2: ElementRef; item = item2falha por causa da análise de atribuição definida. Seu segundo falha pelos mesmos motivos, mas ambos são bem-sucedidos se item2forem inicializados pelos motivos discutidos (ou como uma verificação rápida útil da atribuição que podemos usar declare letaqui). Independentemente disso, é realmente uma pena ver anyuma API pública como essa.
Aluan Haddad 5/03

2

para obter o próximo irmão imediato, use este

event.source._elementRef.nativeElement.nextElementSibling

1

Selecionando o elemento de destino da lista. É fácil selecionar um elemento específico da lista dos mesmos elementos.

código do 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;
}

0

Exemplo mínimo para 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 uma variável de referência de modelo no elemento DOM de interesse. No nosso exemplo, este é #inputElo<input> tag.
  2. Em nossa classe de componente, injete o elemento DOM através do decorador @ViewChild
  3. Acesse o elemento no ngAfterViewInitgancho do ciclo de vida.

Nota:

Se você deseja manipular os elementos DOM, use a API Renderer2 em vez de acessar os elementos diretamente. Permitir o acesso direto ao DOM pode tornar seu aplicativo mais vulnerável a ataques XSS

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.