Come posso selezionare un elemento in un modello di componente?


517

Qualcuno sa come ottenere un elemento definito in un modello di componente? Polymer lo rende davvero facile con $e $$.

Mi stavo solo chiedendo come procedere in Angular.

Prendi l'esempio dal 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;
    }
}

Come afferro o ottengo un riferimento dell'elemento po inputdalla definizione della classe?

Risposte:


939

Invece di iniettare ElementRefe utilizzare querySelectoro simili da lì, è possibile utilizzare un modo dichiarativo per accedere direttamente agli elementi nella vista:

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

elemento

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

Esempio StackBlitz

  • @ViewChild () supporta la direttiva o il tipo di componente come parametro o il nome (stringa) di una variabile modello.
  • @ViewChildren () supporta anche un elenco di nomi come elenco separato da virgole (attualmente non sono ammessi spazi @ViewChildren('var1,var2,var3')).
  • @ContentChild () e @ContentChildren () fanno lo stesso ma alla luce DOM ( <ng-content>elementi proiettati).

discendenti

@ContentChildren() è l'unico che consente di eseguire query anche per i discendenti

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

{descendants: true}dovrebbe essere il valore predefinito ma non è in 2.0.0 final ed è considerato un bug.
È stato risolto in 2.0.1

leggere

Se sono presenti un componente e direttive, il readparametro consente di specificare quale istanza deve essere restituita.

Ad esempio, ViewContainerRefciò è richiesto dai componenti creati dinamicamente anziché dal valore predefinitoElementRef

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

iscriviti alle modifiche

Anche se view children è impostato solo quando ngAfterViewInit()viene chiamato e content child è impostato solo quando ngAfterContentInit()viene chiamato, se si desidera iscriversi alle modifiche del risultato della query, è necessariongOnInit()

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

accesso DOM diretto

può solo interrogare elementi DOM, ma non componenti o istanze della direttiva:

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

ottenere contenuti proiettati arbitrari

Vedi Accedi al contenuto escluso


12
I team angolari hanno sconsigliato di utilizzare ElementRef, questa è la soluzione migliore.
Onorevole Chow,

7
In realtà inputè anche un ElementRef, ma ottieni il riferimento all'elemento che desideri effettivamente, invece di interrogarlo dall'host ElementRef.
Günter Zöchbauer,

32
In realtà l'utilizzo ElementRefva bene. Anche usare ElementRef.nativeElementcon Rendererva bene. Ciò che è scoraggiato è l'accesso ElementRef.nativeElement.xxxdiretto alle proprietà .
Günter Zöchbauer,

2
@Natanael Non so se o dove questo sia esplicitamente documentato, ma viene regolarmente menzionato in problemi o altre discussioni (anche da parte dei membri del team angolare) che si dovrebbe evitare l'accesso diretto al DOM. L'accesso diretto al DOM (che è ciò che accede alle proprietà e ai metodi di ElementRef.nativeElement), ti impedisce di utilizzare il rendering lato server Angulars e la funzione WebWorker (non so se interrompe anche il compilatore di modelli offline in arrivo - ma suppongo di no).
Günter Zöchbauer,

10
Come accennato in precedenza nella sezione di lettura , se si desidera ottenere nativeElement per un elemento con ViewChild, è necessario effettuare le seguenti operazioni: @ViewChild('myObj', { read: ElementRef }) myObj: ElementRef;
jsgoupil

203

Puoi ottenere un handle per l'elemento DOM tramite ElementRefiniettandolo nel costruttore del tuo componente:

constructor(myElement: ElementRef) { ... }

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


1
@Brocco puoi aggiornare la tua risposta? Mi piacerebbe vedere una soluzione attuale poiché non ElementRefc'è più.
Jefftopia,

23
ElementRefè disponibile (di nuovo?).
Günter Zöchbauer,

10
link Utilizzare questa API come ultima risorsa quando è necessario l'accesso diretto al DOM. Utilizzare invece il modello e l'associazione dati forniti da Angular. In alternativa, dai un'occhiata a Renderer che fornisce API che possono essere tranquillamente utilizzate anche quando l'accesso diretto agli elementi nativi non è supportato. Affidarsi all'accesso diretto al DOM crea un accoppiamento stretto tra l'applicazione e i livelli di rendering, il che renderà impossibile separare i due e distribuire l'applicazione in un web worker.
sandeep talabathula,

@sandeeptalabathula Qual è un'opzione migliore per trovare un elemento a cui collegare un componente di selezione data mobile da una libreria di terze parti? Sono consapevole che questa non era la domanda originale, ma fai capire che trovare elementi nel DOM è negativo in tutti gli scenari ...
John

13
@john Ah .. ok. Puoi provare questo this.element.nativeElement.querySelector('#someElementId')e passare ElementRef al costruttore in questo modo. 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) {
    // ...
  }
}

Esempio aggiornato per funzionare con l'ultima versione

Per maggiori dettagli sull'elemento nativo, qui


20

Angolare 4+ : utilizzare renderer.selectRootElementcon un selettore CSS per accedere all'elemento.

Ho un modulo che inizialmente mostra un input e-mail. Dopo aver inserito l'e-mail, il modulo verrà espanso per consentire loro di continuare ad aggiungere informazioni relative al loro progetto. Tuttavia, se sono senza un client esistente, il modulo sarà includono una sezione di indirizzo sopra la sezione informazioni del progetto.

A partire da ora, la parte di immissione dei dati non è stata suddivisa in componenti, quindi le sezioni sono gestite con * ngIf direttive. Devo concentrarmi sul campo delle note del progetto se sono un client esistente o sul campo del nome se sono nuovi.

Ho provato le soluzioni senza successo. Tuttavia, l'aggiornamento 3 in questa risposta mi ha dato la metà dell'eventuale soluzione. L'altra metà è venuta dalla risposta di MatteoNY in questo thread. Il risultato è questo:

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

Poiché l'unica cosa che sto facendo è focalizzare l'attenzione su un elemento, non ho bisogno di preoccuparmi del rilevamento delle modifiche, quindi posso effettivamente eseguire la chiamata a renderer.selectRootElement fuori di Angular. Poiché è necessario concedere il tempo necessario per il rendering delle nuove sezioni, la sezione dell'elemento è racchiusa in un timeout per consentire ai thread di rendering di recuperare il tempo prima che venga tentata la selezione dell'elemento. Una volta impostato tutto, posso semplicemente chiamare l'elemento usando i selettori CSS di base.

So che questo esempio si è occupato principalmente dell'evento focus, ma per me è difficile che non possa essere utilizzato in altri contesti.


La classe Renderer è DEPRECATA da Angular 4.3.0. angular.io/api/core/Renderer
Jamie il

15

Per le persone che cercano di afferrare l'istanza del componente all'interno di un *ngIfo*ngSwitchCase , è possibile seguire questo trucco.

Crea una initdirettiva.

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

Esporta il tuo componente con un nome come myComponent

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

Utilizzare questo modello per ottenere l' istanza ElementRefANDMyComponent

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

Usa questo codice in TypeScript

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

12

importare il ViewChilddecoratore da@angular/core , in questo modo:

Codice HTML:

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

Codice TS:

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

class TemplateFormComponent {

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

ora puoi usare l'oggetto 'myForm' per accedere a qualsiasi elemento al suo interno nella classe.

Source


Ma dovresti notare che quasi non devi accedere agli elementi del modello nella classe del componente, devi solo capire bene la logica angolare.
Hany

3
Non usare nessuno, il 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
Fornisci alcune spiegazioni a questo codice. Il semplice codice di dumping senza spiegazione è altamente scoraggiato.
Rayryeng,

5
Non funzionerà: gli attributi impostati tramite le annotazioni @ViewChild saranno disponibili solo dopo l'evento ngAfterViewInit del ciclo di vita. L'accesso al valore nel costruttore produrrebbe un valore indefinito per inputTxtin quel caso.
David M.

Usando ang 7, ha funzionato perfettamente.
MizAkita,

5

Nota : questo non vale per Angular 6 e versioni successive come èElementRefstato indicatoElementRef<T>conTil tipo dinativeElement .

Vorrei aggiungere che se stai usando ElementRef, come raccomandato da tutte le risposte, allora incontrerai immediatamente il problema che ElementRefha una terribile dichiarazione di tipo che sembra

export declare class ElementRef {
  nativeElement: any;
}

questo è stupido in un ambiente browser in cui nativeElement è un HTMLElement .

Per ovviare a questo è possibile utilizzare la seguente tecnica

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

interface ElementRef {
  nativeElement: HTMLElement;
}

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

1
Questo spiega un problema che stavo riscontrando. Questo non funziona perché si dirà itemdeve essere un ElementRef, anche se si sta impostando in un altro ElementRef: let item:ElementRef, item2:ElementRef; item = item2; // no can do.. Molto confuso. Ma questo va bene: let item:ElementRef, item2:ElementRef; item = item2.nativeElementgrazie all'implementazione che hai sottolineato.
oooyaya,

1
In realtà il tuo primo esempio let item: ElementRef, item2: ElementRef; item = item2fallisce a causa di un'analisi di assegnazione definita. Il tuo secondo errore ha esito negativo per gli stessi motivi, ma entrambi hanno esito positivo se item2viene inizializzato per i motivi discussi (o come utile controllo rapido dell'assegnabilità che è possibile utilizzare declare letqui). Indipendentemente da ciò, è davvero un peccato vedere anysu un'API pubblica come questa.
Aluan Haddad,

2

per ottenere il prossimo fratello immediato, usa questo

event.source._elementRef.nativeElement.nextElementSibling

1

Selezione dell'elemento target dall'elenco. È facile selezionare un particolare elemento dall'elenco degli stessi elementi.

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

codice html:

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

codice css:

.selected {
  color: red;
  background:blue;
}

0

Esempio mimimum per un rapido utilizzo:

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. Inserisci una variabile di riferimento del modello sull'elemento DOM di interesse. Nel nostro esempio questo è #inputElil<input> tag.
  2. Nella nostra classe di componenti iniettare l'elemento DOM tramite il decoratore @ViewChild
  3. Accedi all'elemento nel ngAfterViewInitgancio del ciclo di vita.

Nota:

Se vuoi manipolare gli elementi DOM usa l'API Renderer2 invece di accedere direttamente agli elementi. Consentire l'accesso diretto al DOM può rendere l'applicazione più vulnerabile agli attacchi XSS

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.