Wie kann ich ein Element in einer Komponentenvorlage auswählen?

516

Weiß jemand, wie man ein in einer Komponentenvorlage definiertes Element erhält? Polymer macht es wirklich einfach mit dem $und $$.

Ich habe mich nur gefragt, wie ich in Angular vorgehen soll.

Nehmen Sie das Beispiel aus dem 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;
    }
}

Wie fange ich in der Klassendefinition Halt oder erhalte eine Referenz des Elements poder input?

Aman Gupta
quelle

Antworten:

937

Anstatt von dort aus zu injizieren ElementRefund zu verwenden querySelectoroder ähnliches zu verwenden, kann stattdessen eine deklarative Methode verwendet werden, um direkt auf Elemente in der Ansicht zuzugreifen:

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

Element

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

StackBlitz Beispiel

  • @ViewChild () unterstützt die Direktive oder den Komponententyp als Parameter oder den Namen (String) einer Vorlagenvariablen.
  • @ViewChildren () unterstützt auch eine Liste von Namen als durch Kommas getrennte Liste (derzeit sind keine Leerzeichen zulässig @ViewChildren('var1,var2,var3')).
  • @ContentChild () und @ContentChildren () machen dasselbe, jedoch im hellen DOM ( <ng-content>projizierte Elemente).

Nachkommenschaft

@ContentChildren() ist die einzige, mit der auch Nachkommen abgefragt werden können

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

{descendants: true}sollte die Standardeinstellung sein, ist aber nicht in 2.0.0 final und wird als Fehler angesehen.
Dies wurde in 2.0.1 behoben

lesen

Wenn es eine Komponente und Anweisungen gibt, kann der readParameter angeben, welche Instanz zurückgegeben werden soll.

Dies ist beispielsweise ViewContainerReffür dynamisch erstellte Komponenten anstelle der Standardeinstellung erforderlichElementRef

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

Änderungen abonnieren

Obwohl Ansichtskinder nur festgelegt werden, wenn sie ngAfterViewInit()aufgerufen werden, und Inhaltskinder nur festgelegt werden, wenn sie ngAfterContentInit()aufgerufen werden, sollte dies in erfolgen, wenn Sie Änderungen am Abfrageergebnis abonnieren möchtenngOnInit()

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

direkter DOM-Zugriff

kann nur DOM-Elemente abfragen, aber keine Komponenten oder Direktiveninstanzen:

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

Beliebig projizierten Inhalt erhalten

Siehe Zugriff auf ausgeschlossene Inhalte

Günter Zöchbauer
quelle
12
Die eckigen Teams rieten von der Verwendung von ElementRef ab. Dies ist die bessere Lösung.
Honourable Chow
7
Eigentlich inputist auch ein ElementRef, aber Sie erhalten den Verweis auf das Element, das Sie tatsächlich wollen, anstatt es vom Host abzufragen ElementRef.
Günter Zöchbauer
32
Eigentlich ist die Verwendung ElementRefin Ordnung. Auch mit ElementRef.nativeElementzu verwenden Rendererist in Ordnung. Was davon abgeraten wird, ist der ElementRef.nativeElement.xxxdirekte Zugriff auf Eigenschaften von .
Günter Zöchbauer
2
@ Natanael Ich weiß nicht, ob oder wo dies explizit dokumentiert ist, aber es wird regelmäßig in Problemen oder anderen Diskussionen (auch von Angular-Teammitgliedern) erwähnt, dass ein direkter DOM-Zugriff vermieden werden sollte. Der direkte Zugriff auf das DOM (was der Zugriff auf Eigenschaften und Methoden von ElementRef.nativeElement)ist, verhindert, dass Sie das serverseitige Rendering und die WebWorker-Funktion von Angulars verwenden (ich weiß nicht, ob dadurch auch der bevorstehende Offline-Vorlagen-Compiler beschädigt wird - aber ich denke nicht).
Günter Zöchbauer
10
Wie oben in dem genannten Leseabschnitt, wenn Sie die nativeElement für ein Element mit ViewChild erhalten möchten, müssen Sie folgendes tun: @ViewChild('myObj', { read: ElementRef }) myObj: ElementRef;
jsgoupil
203

Sie können ein Handle für das DOM-Element erhalten, ElementRefindem Sie es in den Konstruktor Ihrer Komponente einfügen:

constructor(myElement: ElementRef) { ... }

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

Brocco
quelle
1
@Brocco kannst du deine Antwort aktualisieren? Ich würde gerne eine aktuelle Lösung sehen, da diese ElementRefweg ist.
Jefftopia
23
ElementRefist verfügbar (wieder?).
Günter Zöchbauer
10
Link Verwenden Sie diese API als letzten Ausweg, wenn ein direkter Zugriff auf DOM erforderlich ist. Verwenden Sie stattdessen Vorlagen und Datenbindungen von Angular. Alternativ können Sie sich Renderer ansehen, das eine API bereitstellt, die auch dann sicher verwendet werden kann, wenn der direkte Zugriff auf native Elemente nicht unterstützt wird. Wenn Sie sich auf den direkten DOM-Zugriff verlassen, wird eine enge Kopplung zwischen Ihrer Anwendung und den Rendering-Ebenen hergestellt, die es unmöglich macht, die beiden zu trennen und Ihre Anwendung in einem Web-Worker bereitzustellen.
Sandeep Talabathula
@sandeeptalabathula Was ist eine bessere Option, um ein Element zu finden, an das eine schwebende Datumsauswahlkomponente aus einer Bibliothek eines Drittanbieters angehängt werden kann? Ich bin mir bewusst, dass dies nicht die ursprüngliche Frage war, aber Sie stellen fest, dass das Finden von Elementen im DOM in allen Szenarien schlecht ist ...
John
13
@ John Ah .. okay. Sie können dies ausprobieren - this.element.nativeElement.querySelector('#someElementId')und ElementRef wie folgt an den Konstruktor übergeben. 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) {
    // ...
  }
}

Beispiel aktualisiert, um mit der neuesten Version zu arbeiten

Weitere Details zum nativen Element finden Sie hier

gdi2290
quelle
20

Winkel 4+ : Verwenden Sie diese Optionrenderer.selectRootElement mit einem CSS-Selektor, um auf das Element zuzugreifen.

Ich habe ein Formular, in dem zunächst eine E-Mail-Eingabe angezeigt wird. Nachdem die E-Mail eingegeben wurde, wird das Formular erweitert, damit sie weiterhin Informationen zu ihrem Projekt hinzufügen können. Wenn es sich jedoch nicht um einen vorhandenen Kunden handelt, enthält das Formular einen Adressabschnitt über dem Projektinformationsabschnitt.

Bis jetzt wurde der Dateneingabeabschnitt nicht in Komponenten aufgeteilt, daher werden die Abschnitte mit * ngIf-Direktiven verwaltet. Ich muss mich auf das Feld Projektnotizen konzentrieren, wenn es sich um einen vorhandenen Client handelt, oder auf das Feld Vorname, wenn es neu ist.

Ich habe die Lösungen ohne Erfolg ausprobiert. Update 3 in dieser Antwort gab mir jedoch die Hälfte der möglichen Lösung. Die andere Hälfte kam von MatteoNYs Antwort in diesem Thread. Das Ergebnis ist folgendes:

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

Da ich mich nur auf ein Element konzentriere, muss ich mich nicht mit der Änderungserkennung befassen, damit ich den Anruf tatsächlich renderer.selectRootElementaußerhalb von Angular ausführen kann . Da ich den neuen Abschnitten Zeit zum Rendern geben muss, wird der Elementabschnitt in eine Zeitüberschreitung eingeschlossen, damit die Rendering-Threads Zeit zum Aufholen haben, bevor die Elementauswahl versucht wird. Sobald alles eingerichtet ist, kann ich das Element einfach mit einfachen CSS-Selektoren aufrufen.

Ich weiß, dass sich dieses Beispiel hauptsächlich mit dem Fokusereignis befasst, aber es fällt mir schwer, dass dies nicht in anderen Kontexten verwendet werden kann.

Neil T.
quelle
Die Klasse Renderer ist seit Angular 4.3.0 DEPRECATED. angle.io/api/core/Renderer
Jamie
15

Für Personen, die versuchen, die Komponenteninstanz in einem *ngIfoder zu erfassen *ngSwitchCase, können Sie diesen Trick befolgen.

Erstellen Sie eine initDirektive.

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

Exportieren Sie Ihre Komponente mit einem Namen wie myComponent

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

Verwenden Sie diese Vorlage, um die ElementRefAND- MyComponentInstanz abzurufen

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

Verwenden Sie diesen Code in TypeScript

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

Importieren Sie den ViewChildDekorateur @angular/corewie folgt:

HTML Quelltext:

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

TS-Code:

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

class TemplateFormComponent {

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

Jetzt können Sie mit dem Objekt 'myForm' auf jedes Element in der Klasse zugreifen.

Source

Hany
quelle
Sie sollten jedoch beachten, dass Sie fast nicht auf Vorlagenelemente in der Komponentenklasse zugreifen müssen, sondern nur die Winkellogik richtig verstehen müssen.
Hany
3
Verwenden Sie keine, der Typ ist 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
quelle
10
Bitte erläutern Sie diesen Code. Von einfachem Code-Dumping ohne Erklärung wird dringend abgeraten.
Rayryeng
5
Dies funktioniert nicht: Attribute, die über @ ViewChild-Annotationen festgelegt wurden, sind erst nach dem Lebenszyklusereignis ngAfterViewInit verfügbar. Der Zugriff auf den Wert im Konstruktor würde inputTxtin diesem Fall einen undefinierten Wert für ergeben .
David M.
Mit ang 7 funktionierte dies einwandfrei.
MizAkita
5

Hinweis : Dies gilt nicht für Angular 6 und höher alsElementRefwurdeElementRef<T>mitTder Art der angibtnativeElement.

Ich möchte hinzufügen, dass, wenn Sie ElementRef, wie in allen Antworten empfohlen, verwenden, Sie sofort auf das Problem stoßen, ElementRefdas eine schreckliche Typdeklaration hat, die aussieht

export declare class ElementRef {
  nativeElement: any;
}

Dies ist in einer Browser-Umgebung, in der nativeElement eine ist, dumm HTMLElement.

Um dies zu umgehen, können Sie die folgende Technik verwenden

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

interface ElementRef {
  nativeElement: HTMLElement;
}

@Component({...}) export class MyComponent {
  constructor(@Inject(ErrorProneElementRef) readonly elementRef: ElementRef) { }
}
Aluan Haddad
quelle
1
Dies erklärt ein Problem, das ich hatte. Dies funktioniert nicht, da es itemsich um ein ElementRef handeln muss, obwohl Sie es auf ein anderes ElementRef setzen : let item:ElementRef, item2:ElementRef; item = item2; // no can do.. Sehr verwirrend. Aber das ist in Ordnung: let item:ElementRef, item2:ElementRef; item = item2.nativeElementwegen der Implementierung, auf die Sie hingewiesen haben.
oooyaya
1
Tatsächlich let item: ElementRef, item2: ElementRef; item = item2schlägt Ihr erstes Beispiel aufgrund einer eindeutigen Zuordnungsanalyse fehl. Ihr zweiter Fehler schlägt aus denselben Gründen fehl, aber beide sind erfolgreich, wenn er item2aus den genannten Gründen initialisiert wurde (oder als nützliche schnelle Überprüfung der Zuweisbarkeit, die wir declare lethier verwenden können). Ungeachtet dessen ist es wirklich eine Schande, anyauf einer öffentlichen API wie dieser zu sehen.
Aluan Haddad
2

Verwenden Sie diese Option, um das nächste Geschwister zu erhalten

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

Zielelement aus der Liste auswählen. Es ist einfach, ein bestimmtes Element aus der Liste derselben Elemente auszuwählen.

Komponentencode:

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 Quelltext:

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

CSS-Code:

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

Minimales Beispiel für eine schnelle Verwendung:

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. Fügen Sie eine Vorlagenreferenzvariable in das interessierende DOM-Element ein. In unserem Beispiel ist dies das #inputElauf dem <input>Tag.
  2. In unserer Komponentenklasse fügen Sie das DOM-Element über den @ ViewChild-Dekorator ein
  3. Greifen Sie auf das Element im ngAfterViewInitLebenszyklus-Hook zu.

Hinweis:

Wenn Sie die DOM-Elemente bearbeiten möchten, verwenden Sie die Renderer2-API, anstatt direkt auf die Elemente zuzugreifen. Durch das Zulassen des direkten Zugriffs auf das DOM wird Ihre Anwendung anfälliger für XSS-Angriffe

Willem van der Veen
quelle