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?

Aman Gupta
fonte

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

Günter Zöchbauer
fonte
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

Brocco
fonte
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

gdi2290
fonte
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.

Neil T.
fonte
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) {
}
jsgoupil
fonte
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

Hany
fonte
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*/
        };
    }
}
Eng.Gabr
fonte
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) { }
}
Aluan Haddad
fonte
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
Apoorv
fonte
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;
}
Sai Goud
fonte
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

Willem van der Veen
fonte