Bagaimana saya bisa memilih elemen dalam templat komponen?

516

Apakah ada yang tahu cara mendapatkan elemen yang didefinisikan dalam templat komponen? Polimer membuatnya sangat mudah dengan $dan $$.

Saya hanya ingin tahu bagaimana melakukannya di Angular.

Ambil contoh dari 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;
    }
}

Bagaimana cara menangkap atau mendapatkan referensi elemen patau inputdari dalam definisi kelas?

Aman Gupta
sumber

Jawaban:

938

Alih-alih menyuntikkan ElementRefdan menggunakan querySelectoratau serupa dari sana, cara deklaratif dapat digunakan sebagai gantinya untuk mengakses elemen dalam tampilan secara langsung:

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

elemen

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

Contoh StackBlitz

  • @ViewChild () mendukung direktif atau tipe komponen sebagai parameter, atau nama (string) dari variabel templat.
  • @ViewChildren () juga mendukung daftar nama sebagai daftar yang dipisahkan koma (saat ini tidak ada spasi yang diizinkan @ViewChildren('var1,var2,var3')).
  • @ContentChild () dan @ContentChildren () melakukan hal yang sama tetapi dalam cahaya DOM ( <ng-content>elemen yang diproyeksikan).

keturunan

@ContentChildren() adalah satu-satunya yang memungkinkan untuk meminta keturunan

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

{descendants: true}seharusnya default tetapi bukan di 2.0.0 final dan itu dianggap bug.
Ini diperbaiki di 2.0.1

Baca

Jika ada komponen dan arahan, readparameter memungkinkan untuk menentukan contoh mana yang harus dikembalikan.

Misalnya ViewContainerRefyang diperlukan oleh komponen yang dibuat secara dinamis, bukan defaultElementRef

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

berlangganan perubahan

Meskipun tampilan anak-anak hanya diatur saat ngAfterViewInit()dipanggil dan konten anak-anak hanya ditetapkan saat ngAfterContentInit()dipanggil, jika Anda ingin berlangganan perubahan hasil permintaan, itu harus dilakukan dalamngOnInit()

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

akses DOM langsung

hanya dapat meminta elemen DOM, tetapi tidak komponen atau instance direktif:

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

dapatkan konten yang diproyeksikan sewenang-wenang

Lihat Mengakses konten yang ditransklusikan

Günter Zöchbauer
sumber
12
Tim sudut menyarankan untuk tidak menggunakan ElementRef, ini adalah solusi yang lebih baik.
Yang Terhormat Chow
7
Sebenarnya inputjuga merupakan ElementRef, tetapi Anda mendapatkan referensi ke elemen yang sebenarnya Anda inginkan, alih-alih menanyakannya dari host ElementRef.
Günter Zöchbauer
32
Sebenarnya menggunakan ElementReftidak apa-apa. Juga menggunakan ElementRef.nativeElementdengan Rendererbaik-baik saja. Yang tidak disarankan adalah mengakses properti ElementRef.nativeElement.xxxsecara langsung.
Günter Zöchbauer
2
@Natanael Saya tidak tahu apakah atau di mana ini secara eksplisit didokumentasikan tetapi disebutkan secara teratur dalam masalah atau diskusi lain (juga dari anggota tim Angular) bahwa akses DOM langsung harus dihindari. Mengakses DOM langsung (yang adalah apa yang mengakses properti dan metode ElementRef.nativeElement)adalah, mencegah Anda dari menggunakan Angulars render sisi server dan fitur WebWorker (saya tidak tahu apakah itu juga istirahat mendatang secara offline Template compiler - tapi kurasa tidak).
Günter Zöchbauer
10
Seperti disebutkan di atas di bagian baca , jika Anda ingin mendapatkan nativeElement untuk elemen dengan ViewChild, Anda harus melakukan hal berikut: @ViewChild('myObj', { read: ElementRef }) myObj: ElementRef;
jsgoupil
203

Anda bisa mendapatkan pegangan ke elemen DOM melalui ElementRefdengan menyuntikkannya ke konstruktor komponen Anda:

constructor(myElement: ElementRef) { ... }

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

Brocco
sumber
1
@Brocco dapatkah Anda memperbarui jawaban Anda? Saya ingin melihat solusi saat ini karena ElementRefhilang.
Jefftopia
23
ElementReftersedia (lagi?).
Günter Zöchbauer
10
tautan Gunakan API ini sebagai pilihan terakhir saat diperlukan akses langsung ke DOM. Gunakan templating dan pengikatan data yang disediakan oleh Angular sebagai gantinya. Atau Anda bisa melihat Renderer yang menyediakan API yang dapat digunakan dengan aman bahkan ketika akses langsung ke elemen asli tidak didukung. Mengandalkan akses DOM langsung menciptakan hubungan yang erat antara aplikasi Anda dan rendering layer yang akan membuatnya tidak mungkin untuk memisahkan keduanya dan menyebarkan aplikasi Anda menjadi pekerja web.
sandeep talabathula
@sandeeptalabathula Apa pilihan yang lebih baik untuk menemukan elemen untuk melampirkan komponen pemilih tanggal mengambang dari perpustakaan pihak ketiga ke? Saya sadar bahwa ini bukan pertanyaan awal, tetapi Anda dapat menemukan bahwa menemukan elemen di DOM buruk dalam semua skenario ...
John
13
@ John Ah .. baiklah. Anda dapat mencoba ini - this.element.nativeElement.querySelector('#someElementId')dan meneruskan ElementRef ke konstruktor seperti ini .. private element: ElementRef, Impor 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) {
    // ...
  }
}

Contoh diperbarui untuk bekerja dengan versi terbaru

Untuk detail lebih lanjut tentang elemen asli, di sini

gdi2290
sumber
20

Angular 4+ : Gunakan renderer.selectRootElementdengan pemilih CSS untuk mengakses elemen.

Saya punya formulir yang awalnya menampilkan input email. Setelah email dimasukkan, formulir akan diperluas untuk memungkinkan mereka untuk terus menambahkan informasi yang berkaitan dengan proyek mereka. Namun, jika mereka bukan klien yang sudah ada, formulir akan menyertakan bagian alamat di atas bagian informasi proyek.

Sampai sekarang, bagian entri data belum dipecah menjadi komponen, sehingga bagian dikelola dengan arahan * ngIf. Saya perlu menetapkan fokus pada bidang catatan proyek jika mereka adalah klien yang sudah ada, atau bidang nama depan jika mereka baru.

Saya mencoba solusi tanpa hasil. Namun, Pembaruan 3 dalam jawaban ini memberi saya setengah dari solusi akhirnya. Setengah lainnya datang dari tanggapan MatteoNY di utas ini . Hasilnya adalah ini:

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

Karena satu-satunya hal yang saya lakukan adalah mengatur fokus pada elemen, saya tidak perlu khawatir dengan deteksi perubahan, jadi saya benar-benar dapat menjalankan panggilan ke renderer.selectRootElementluar Angular. Karena saya perlu memberi waktu pada bagian baru untuk dirender, bagian elemen dibungkus dengan batas waktu untuk memungkinkan waktu rendering thread untuk mengejar ketinggalan sebelum pemilihan elemen dilakukan. Setelah semua yang diatur, saya bisa memanggil elemen menggunakan pemilih CSS dasar.

Saya tahu contoh ini terutama berkaitan dengan acara fokus, tetapi sulit bagi saya bahwa ini tidak dapat digunakan dalam konteks lain.

Neil T.
sumber
Renderer kelas DIHAPUS sejak Angular 4.3.0. angular.io/api/core/Renderer
Jamie
15

Untuk orang yang mencoba mengambil instance komponen di dalam *ngIfatau *ngSwitchCase, Anda dapat mengikuti trik ini.

Buat initarahan.

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

Ekspor komponen Anda dengan nama seperti myComponent

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

Gunakan templat ini untuk mendapatkan instance ElementRefANDMyComponent

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

Gunakan kode ini dalam TypeScript

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

impor ViewChilddekorator @angular/core, seperti:

Kode HTML:

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

Kode TS:

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

class TemplateFormComponent {

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

sekarang Anda dapat menggunakan objek 'myForm' untuk mengakses elemen apa pun di dalamnya di dalam kelas.

Source

Hany
sumber
Tetapi Anda harus memperhatikan bahwa Anda hampir tidak perlu mengakses elemen template di kelas komponen, Anda hanya perlu memahami logika sudut dengan benar.
Hany
3
Jangan gunakan apa pun, tipenya 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
sumber
10
Harap berikan beberapa penjelasan untuk kode ini. Cukup kode dumping tanpa penjelasan sangat tidak disarankan.
rayryeng
5
Ini tidak akan berfungsi: atribut yang ditetapkan melalui penjelasan @ViewChild hanya akan tersedia setelah acara siklus hidup ngAfterViewInit. Mengakses nilai dalam konstruktor akan menghasilkan nilai yang tidak ditentukan untuk inputTxtkasus tersebut.
David M.
Menggunakan ang 7, ini bekerja dengan sempurna.
MizAkita
5

Catatan : ini tidak berlaku untuk sudut 6 dan di atas sebagaiElementRefmenjadiElementRef<T>denganTyang menunjukkan jenisnativeElement.

Saya ingin menambahkan bahwa jika Anda menggunakan ElementRef, seperti yang direkomendasikan oleh semua jawaban, maka Anda akan segera menghadapi masalah yang ElementRefmemiliki deklarasi tipe mengerikan yang terlihat seperti

export declare class ElementRef {
  nativeElement: any;
}

ini bodoh di lingkungan peramban tempat nativeElement adalah HTMLElement.

Untuk mengatasinya, Anda dapat menggunakan teknik berikut

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

interface ElementRef {
  nativeElement: HTMLElement;
}

@Component({...}) export class MyComponent {
  constructor(@Inject(ErrorProneElementRef) readonly elementRef: ElementRef) { }
}
Aluan Haddad
sumber
1
Ini menjelaskan masalah yang saya alami. Ini tidak bekerja karena itu akan mengatakan itemkebutuhan untuk menjadi ElementRef, meskipun Anda menetapkan ke ElementRef lain: let item:ElementRef, item2:ElementRef; item = item2; // no can do.. Sangat membingungkan. Tapi ini bagus: let item:ElementRef, item2:ElementRef; item = item2.nativeElementkarena implementasi yang Anda tunjukkan.
oooyaya
1
Sebenarnya contoh pertama Anda let item: ElementRef, item2: ElementRef; item = item2gagal karena analisis tugas yang pasti. Kedua Anda gagal karena alasan yang sama tetapi keduanya berhasil jika item2diinisialisasi karena alasan yang dibahas (atau sebagai pemeriksaan cepat yang berguna untuk penugasan yang dapat kami gunakan di declare letsini). Bagaimanapun juga, sungguh memalukan melihat anyAPI publik seperti ini.
Aluan Haddad
2

untuk mendapatkan saudara segera berikutnya, gunakan ini

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

Memilih elemen target dari daftar. Sangat mudah untuk memilih elemen tertentu dari daftar elemen yang sama.

kode komponen:

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

kode html:

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

kode css:

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

Contoh minimum untuk penggunaan cepat:

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. Letakkan variabel referensi templat pada elemen DOM yang menarik. Dalam contoh kita ini adalah #inputElpada <input>tag.
  2. Di kelas komponen kami menyuntikkan elemen DOM melalui dekorator @ViewChild
  3. Akses elemen dalam ngAfterViewInitkait siklus hidup.

catatan:

Jika Anda ingin memanipulasi elemen DOM gunakan Renderer2 API alih-alih mengakses elemen secara langsung. Mengizinkan akses langsung ke DOM dapat membuat aplikasi Anda lebih rentan terhadap serangan XSS

Willem van der Veen
sumber