Jak mogę wybrać element w szablonie komponentu?

516

Czy ktoś wie, jak zdobyć element zdefiniowany w szablonie komponentu? Dzięki polimerowi jest to naprawdę łatwe dzięki $i $$.

Zastanawiałem się, jak to zrobić w Angular.

Weź przykład z samouczka:

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

Jak złapać lub uzyskać odwołanie do elementu plub inputz definicji klasy?

Aman Gupta
źródło

Odpowiedzi:

937

Zamiast wstrzykiwania ElementRefi używania querySelectorlub stamtąd stamtąd można zastosować deklaratywny sposób, aby uzyskać bezpośredni dostęp do elementów w widoku:

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

element

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

Przykład StackBlitz

  • @ViewChild () obsługuje dyrektywę lub typ komponentu jako parametr lub nazwę (ciąg) zmiennej szablonu.
  • @ViewChildren () obsługuje również listę nazw jako listę oddzieloną przecinkami (obecnie nie można używać spacji @ViewChildren('var1,var2,var3')).
  • @ContentChild () i @ContentChildren () robią to samo, ale w lekkim DOM ( <ng-content>rzutowane elementy).

potomków

@ContentChildren() jest jedynym, który pozwala również wyszukiwać potomków

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

{descendants: true}powinien być domyślny, ale nie jest w wersji 2.0.0 końcowej i jest uważany za błąd
Zostało to naprawione w wersji 2.0.1

czytać

Jeśli istnieje komponent i dyrektywy, readparametr pozwala określić, która instancja ma zostać zwrócona.

Na przykład ViewContainerRefjest to wymagane przez dynamicznie tworzone komponenty zamiast domyślnychElementRef

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

subskrybuj zmiany

Mimo że elementy potomne widoku są ustawiane tylko wtedy, gdy ngAfterViewInit()są wywoływane, a elementy potomne treści są ustawiane tylko wtedy, gdy ngAfterContentInit()są wywoływane, jeśli chcesz zasubskrybować zmiany wyniku zapytania, należy to zrobić wngOnInit()

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

bezpośredni dostęp do DOM

może wyszukiwać tylko elementy DOM, ale nie instancje komponentów lub dyrektyw:

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

uzyskać dowolną wyświetlaną treść

Zobacz Dostęp do treści zastrzeżonych

Günter Zöchbauer
źródło
12
Zespoły kątowe odradzają stosowanie ElementRef, jest to lepsze rozwiązanie.
Honorowy Chow
7
Właściwie inputjest również ElementRef, ale dostajesz odwołanie do elementu, którego faktycznie potrzebujesz, zamiast zapytania go z hosta ElementRef.
Günter Zöchbauer
32
Właściwie używanie ElementRefjest w porządku. Również używanie ElementRef.nativeElementz Rendererjest w porządku. Co jest odradzane jest dostęp do właściwości ElementRef.nativeElement.xxxbezpośrednio.
Günter Zöchbauer
2
@Natanael Nie wiem, czy i gdzie jest to wyraźnie udokumentowane, ale jest regularnie wymieniane w kwestiach lub innych dyskusjach (także od członków zespołu Angular), że należy unikać bezpośredniego dostępu do DOM. Bezpośredni dostęp do DOM (co zapewnia dostęp do właściwości i metod ElementRef.nativeElement), uniemożliwia korzystanie z renderowania po stronie serwera Angulars i funkcji WebWorker (nie wiem, czy to również psuje nadchodzący kompilator szablonów offline - ale chyba nie).
Günter Zöchbauer
10
Jak wspomniano powyżej w sekcji do czytania , jeśli chcesz uzyskać parametr nativeElement dla elementu z ViewChild, musisz wykonać następujące czynności: @ViewChild('myObj', { read: ElementRef }) myObj: ElementRef;
jsgoupil
203

Możesz uzyskać uchwyt do elementu DOM poprzez ElementRefwstrzyknięcie go do konstruktora komponentu:

constructor(myElement: ElementRef) { ... }

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

Brocco
źródło
1
@Brocco czy możesz zaktualizować swoją odpowiedź? Chciałbym zobaczyć obecne rozwiązanie, ponieważ ElementRefjuż go nie ma.
Jefftopia
23
ElementRefjest dostępny (ponownie?).
Günter Zöchbauer
10
link Użyj tego interfejsu API jako ostateczności, gdy potrzebny jest bezpośredni dostęp do DOM. Zamiast tego użyj szablonów i wiązania danych dostarczonych przez Angular. Alternatywnie, spójrz na Renderer, który zapewnia API, z którego można bezpiecznie korzystać, nawet gdy bezpośredni dostęp do elementów natywnych nie jest obsługiwany. Poleganie na bezpośrednim dostępie do DOM tworzy ścisłe sprzężenie między aplikacją a warstwami renderowania, co uniemożliwi oddzielenie tych dwóch i wdrożenie aplikacji w procesie roboczym.
sandeep talabathula
@sandeeptalabathula Jaka jest lepsza opcja znalezienia elementu, do którego można dołączyć komponent wyboru zmiennoprzecinkowego z biblioteki innej firmy? Wiem, że to nie było oryginalne pytanie, ale zdajesz sobie sprawę, że znalezienie elementów w DOM jest złe we wszystkich scenariuszach ...
John
13
@john Ah .. dobrze. Możesz wypróbować to - this.element.nativeElement.querySelector('#someElementId')i przekazać ElementRef do konstruktora w ten sposób .. private element: ElementRef, Importuj 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) {
    // ...
  }
}

Przykład zaktualizowany do pracy z najnowszą wersją

Aby uzyskać więcej informacji na temat elementu natywnego, tutaj

gdi2290
źródło
20

Angular 4+ : Użyj renderer.selectRootElementz selektorem CSS, aby uzyskać dostęp do elementu.

Mam formularz, który początkowo wyświetla dane e-mail. Po wprowadzeniu wiadomości e-mail formularz zostanie rozwinięty, aby umożliwić im dalsze dodawanie informacji związanych z ich projektem. Jednakże, jeśli są one nie istniejącego klienta, forma będzie zawierać sekcję powyższy adres w sekcji informacji o projekcie.

Na razie część wprowadzania danych nie została podzielona na komponenty, więc sekcjami zarządza się dyrektywami * ngIf. Muszę skupić się na polu notatek projektowych, jeśli są istniejącym klientem, lub polu imienia, jeśli są nowi.

Próbowałem rozwiązań bez powodzenia. Jednak aktualizacja 3 w tej odpowiedzi dała mi połowę ostatecznego rozwiązania. Druga połowa pochodziła z odpowiedzi MatteoNY w tym wątku. Rezultat jest następujący:

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

Ponieważ jedyne, co robię, to skupianie się na elemencie, nie muszę się martwić wykrywaniem zmian, więc mogę uruchomić połączenie renderer.selectRootElementpoza Angularem. Ponieważ muszę dać nowym sekcjom czas na wyrenderowanie, sekcja elementu jest zawijana w limicie czasu, aby pozwolić wątkom renderującym na nadrobienie zaległości przed próbą wyboru elementu. Po zakończeniu konfiguracji mogę po prostu wywołać element za pomocą podstawowych selektorów CSS.

Wiem, że ten przykład dotyczył głównie zdarzenia skupienia, ale dla mnie ciężko jest, aby nie można go było użyć w innych kontekstach.

Neil T.
źródło
Klasa Renderer jest DEPRECATED od wersji Angular 4.3.0. angular.io/api/core/Renderer
Jamie
15

Dla ludzi, którzy próbują złapać instancję składnika wewnątrz *ngIflub *ngSwitchCasemożna śledzić tę sprawę.

Utwórz initdyrektywę.

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

Wyeksportuj komponent o nazwie takiej jak myComponent

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

Użyj tego szablonu, aby uzyskać instancję ElementRefANDMyComponent

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

Użyj tego kodu w TypeScript

init(myComponentRef: ElementRef, myComponent: MyComponent) {
}
jsgoupil
źródło
12

zaimportuj ViewChilddekorator z @angular/core:

Kod HTML:

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

Kod TS:

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

class TemplateFormComponent {

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

teraz możesz użyć obiektu „myForm”, aby uzyskać dostęp do dowolnego elementu w klasie.

Source

Hany
źródło
Należy jednak zauważyć, że prawie nie trzeba uzyskiwać dostępu do elementów szablonu w klasie komponentów, wystarczy dobrze zrozumieć logikę kątową.
Hany
3
Nie używaj żadnych, typ to 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
źródło
10
Podaj wyjaśnienie tego kodu. Odrzucanie kodu bez wyjaśnienia jest wysoce odradzane.
rayryeng
5
To nie zadziała: atrybuty ustawione za pomocą adnotacji @ViewChild będą dostępne tylko po zdarzeniu cyklu życia ngAfterViewInit. Uzyskiwanie dostępu do wartości w konstruktorze dałoby inputTxtw tym przypadku nieokreśloną wartość .
David M.
Przy użyciu ang 7 działało to bezbłędnie.
MizAkita
5

Uwaga : to nie ma zastosowania do kątowego 6 i powyżej jakoElementRefstałaElementRef<T> z Toznaczeniem typu nativeElement.

Chciałbym dodać, że jeśli używasz ElementRef, zgodnie z zaleceniami wszystkich odpowiedzi, natychmiast natkniesz się na problem ElementRefz okropną deklaracją typu, która wygląda jak

export declare class ElementRef {
  nativeElement: any;
}

jest to głupie w środowisku przeglądarki, w którym nativeElement jest HTMLElement .

Aby obejść ten problem, możesz użyć następującej techniki

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

interface ElementRef {
  nativeElement: HTMLElement;
}

@Component({...}) export class MyComponent {
  constructor(@Inject(ErrorProneElementRef) readonly elementRef: ElementRef) { }
}
Aluan Haddad
źródło
1
To wyjaśnia problem, który miałem. To nie działa, ponieważ będzie to powiedzieć itempotrzeby być ElementRef, choć jesteś ustawienie go do innego ElementRef: let item:ElementRef, item2:ElementRef; item = item2; // no can do.. Bardzo mylące. Ale to dobrze: let item:ElementRef, item2:ElementRef; item = item2.nativeElementze względu na implementację, którą wskazałeś.
oooyaya
1
W rzeczywistości pierwszy przykład let item: ElementRef, item2: ElementRef; item = item2kończy się niepowodzeniem z powodu określonej analizy przypisania. Druga próba kończy się niepowodzeniem z tych samych powodów, ale oba kończą się powodzeniem, jeśli item2zostały zainicjowane z omówionych powodów (lub jako przydatna szybka kontrola możliwości przypisania, którą możemy wykorzystać declare lettutaj). Niezależnie od tego, naprawdę szkoda zobaczyć anytakie publiczne API.
Aluan Haddad
2

aby uzyskać najbliższe następne rodzeństwo, użyj tego

event.source._elementRef.nativeElement.nextElementSibling
Apoorv
źródło
1

Wybieranie elementu docelowego z listy. Łatwo jest wybrać konkretny element z listy tych samych elementów.

kod komponentu:

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

Kod HTML:

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

kod css:

.selected {
  color: red;
  background:blue;
}
Sai Goud
źródło
0

Minimalny przykład szybkiego użycia:

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. Umieść zmienną odniesienia szablonu na odpowiednim elemencie DOM. W naszym przykładzie jest #inputElto <input>tag.
  2. W naszej klasie komponentów wstaw element DOM za pomocą dekoratora @ViewChild
  3. Uzyskaj dostęp do elementu w ngAfterViewInithaku cyklu życia.

Uwaga:

Jeśli chcesz manipulować elementami DOM, użyj interfejsu API Renderer2 zamiast bezpośredniego dostępu do elementów. Zezwolenie na bezpośredni dostęp do DOM może zwiększyć podatność aplikacji na ataki XSS

Willem van der Veen
źródło