ฉันจะเลือกองค์ประกอบในเทมเพลตองค์ประกอบได้อย่างไร


516

ไม่มีใครรู้วิธีการได้รับองค์ประกอบที่กำหนดไว้ในแม่แบบองค์ประกอบหรือไม่? พอลิเมอทำให้เป็นเรื่องง่ายด้วยและ$$$

ฉันแค่สงสัยว่าจะไปเกี่ยวกับเรื่องนี้ในเชิงมุม

นำตัวอย่างจากบทช่วยสอน:

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

ฉันจะจับหรือรับการอ้างอิงของpหรือinputองค์ประกอบจากภายในคำจำกัดความของชั้นเรียนได้อย่างไร

คำตอบ:


937

แทนที่จะเป็นการฉีดElementRefและใช้querySelectorหรือคล้ายกันจากที่นั่นสามารถใช้วิธีการประกาศแทนการเข้าถึงองค์ประกอบในมุมมองโดยตรง:

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

ธาตุ

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

ตัวอย่าง StackBlitz

  • @ViewChild ()รองรับคำสั่งหรือประเภทส่วนประกอบเป็นพารามิเตอร์หรือชื่อ (สตริง) ของตัวแปรแม่แบบ
  • @ViewChildren ()ยังสนับสนุนรายชื่อเป็นรายการที่คั่นด้วยเครื่องหมายจุลภาค (ปัจจุบันไม่อนุญาตให้เว้นวรรค@ViewChildren('var1,var2,var3'))
  • @ContentChild ()และ@ContentChildren ()ทำเช่นเดียวกัน แต่ใน DOM แสง ( <ng-content>องค์ประกอบที่คาดการณ์)

ลูกหลาน

@ContentChildren() เป็นคนเดียวที่อนุญาตให้สอบถามด้วยเพื่อสืบทอด

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

{descendants: true}ควรเป็นค่าเริ่มต้น แต่ไม่ได้อยู่ใน 2.0.0 สุดท้ายและถือเป็นข้อผิดพลาด
นี่คือการแก้ไขใน 2.0.1

อ่าน

หากมีส่วนประกอบและคำสั่งreadพารามิเตอร์อนุญาตให้ระบุอินสแตนซ์ที่ควรส่งคืน

ตัวอย่างเช่นViewContainerRefจำเป็นโดยคอมโพเนนต์ที่สร้างขึ้นแบบไดนามิกแทนค่าเริ่มต้นElementRef

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

สมัครรับการเปลี่ยนแปลง

แม้ว่า view children จะถูกตั้งค่าเมื่อngAfterViewInit()มีการเรียกเท่านั้นและ content children จะถูกตั้งค่าเมื่อngAfterContentInit()มีการเรียกเท่านั้นหากคุณต้องการสมัครรับข้อมูลการเปลี่ยนแปลงผลการสืบค้นควรทำในngOnInit()

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

การเข้าถึง DOM โดยตรง

สามารถสืบค้นองค์ประกอบ DOM เท่านั้น แต่ไม่สามารถใช้องค์ประกอบหรืออินสแตนซ์คำสั่ง:

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

รับเนื้อหาที่ฉายตามอำเภอใจ

ดูที่การเข้าถึงเนื้อหาที่รวมอยู่


12
ทีมเชิงมุมแนะนำให้ใช้ ElementRef นี่เป็นทางออกที่ดีกว่า
เกียรติ Chow

7
ที่จริงinputยังเป็นแต่คุณได้รับการอ้างอิงถึงองค์ประกอบที่คุณต้องการจริงแทนการสอบถามจากโฮสต์ElementRef ElementRef
GünterZöchbauer

32
การใช้งานจริงElementRefนั้นใช้ได้ ยังใช้ElementRef.nativeElementกับRendererดี สิ่งที่ท้อแท้คือการเข้าถึงคุณสมบัติของElementRef.nativeElement.xxxโดยตรง
GünterZöchbauer

2
@Natanael ฉันไม่รู้ว่าหรือที่ไหนที่มีการบันทึกไว้อย่างชัดเจน แต่มีการกล่าวถึงเป็นประจำในประเด็นหรือการอภิปรายอื่น ๆ (เช่นจากสมาชิกในทีมของ Angular) ที่ควรหลีกเลี่ยงการเข้าถึง DOM โดยตรง การเข้าถึง DOM โดยตรง (ซึ่งเป็นสิ่งที่เข้าถึงคุณสมบัติและวิธีการElementRef.nativeElement)คือป้องกันคุณจากการใช้การแสดงผลฝั่งเซิร์ฟเวอร์ของ Angulars และคุณสมบัติ WebWorker (ฉันไม่รู้ว่ามันแบ่งตัวรวบรวมเทมเพลตออฟไลน์ที่กำลังมาถึง - แต่ฉันเดาไม่ได้)
Günter Zöchbauer

10
ดังกล่าวข้างต้นในส่วนอ่านถ้าคุณต้องการรับ nativeElement สำหรับองค์ประกอบด้วย ViewChild คุณต้องทำสิ่งต่อไปนี้: @ViewChild('myObj', { read: ElementRef }) myObj: ElementRef;
jsgoupil

203

คุณสามารถจัดการกับองค์ประกอบ DOM ผ่านElementRefโดยการแทรกลงในคอนสตรัคเตอร์ของส่วนประกอบ:

constructor(myElement: ElementRef) { ... }

เอกสาร: https://angular.io/docs/ts/latest/api/core/index/ElementRef-class.html


1
@Brocco คุณสามารถอัปเดตคำตอบของคุณได้ไหม? ฉันต้องการที่จะเห็นทางออกปัจจุบันตั้งแต่ElementRefหายไป
Jefftopia

23
ElementRefสามารถใช้ได้ (อีกครั้ง?)
GünterZöchbauer

10
ลิงก์ ใช้ API นี้เป็นทางเลือกสุดท้ายเมื่อต้องการการเข้าถึง DOM โดยตรง ใช้การสร้างเทมเพลตและการเชื่อมโยงข้อมูลที่จัดทำโดย Angular แทน หรือคุณดู Renderer ซึ่งมี API ที่สามารถใช้ได้อย่างปลอดภัยแม้ว่าจะไม่รองรับการเข้าถึงองค์ประกอบพื้นฐานโดยตรง การพึ่งพาการเข้าถึงโดยตรงของ DOM สร้างการเชื่อมต่อที่แน่นหนาระหว่างแอปพลิเคชันของคุณและเลเยอร์การแสดงผลซึ่งจะทำให้ไม่สามารถแยกทั้งสองแอปพลิเคชันและปรับใช้แอปพลิเคชันของคุณให้เป็นผู้ปฏิบัติงานเว็บ
sandeep talabathula

@sandeeptalabathula เป็นตัวเลือกที่ดีกว่าสำหรับการค้นหาองค์ประกอบที่จะแนบองค์ประกอบตัวเลือกวันที่ลอยจากห้องสมุดบุคคลที่สามคืออะไร? ฉันรู้ว่านี่ไม่ใช่คำถามเดิม แต่คุณทำให้การค้นหาองค์ประกอบใน DOM นั้นไม่ดีในทุกสถานการณ์ ...
John

13
@john Ah .. โอเค คุณอาจลองทำสิ่งนี้ - this.element.nativeElement.querySelector('#someElementId')และส่ง ElementRef ให้ผู้สร้างเช่นนี้ .. private element: ElementRef, นำเข้า 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) {
    // ...
  }
}

ตัวอย่างอัปเดตเพื่อใช้งานกับเวอร์ชันล่าสุด

สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับองค์ประกอบพื้นฐานได้ที่นี่


20

Angular 4+ : ใช้renderer.selectRootElementกับตัวเลือก CSS เพื่อเข้าถึงองค์ประกอบ

ฉันมีแบบฟอร์มที่แสดงการป้อนอีเมลในตอนแรก หลังจากป้อนอีเมลแล้วแบบฟอร์มจะถูกขยายเพื่อให้พวกเขาสามารถเพิ่มข้อมูลที่เกี่ยวข้องกับโครงการของพวกเขาต่อไป อย่างไรก็ตามหากพวกเขาไม่ใช่ลูกค้าที่มีอยู่แบบฟอร์มจะรวมส่วนที่อยู่เหนือส่วนข้อมูลโครงการ

ณ ตอนนี้ส่วนการป้อนข้อมูลยังไม่ได้ถูกแยกออกเป็นส่วนประกอบดังนั้นส่วนต่างๆจึงได้รับการจัดการด้วยคำสั่ง * ngIf ฉันจำเป็นต้องตั้งค่าโฟกัสบนฟิลด์บันทึกย่อของโครงการหากพวกเขาเป็นลูกค้าที่มีอยู่หรือฟิลด์ชื่อถ้าพวกเขาใหม่

ฉันลองวิธีแก้ปัญหาโดยไม่ประสบความสำเร็จ อย่างไรก็ตามการอัพเดต 3 ในคำตอบนี้ให้ครึ่งหนึ่งของการแก้ปัญหาในที่สุด อีกครึ่งหนึ่งมาจากการตอบสนอง MatteoNY ในนี้ด้าย ผลลัพธ์คือ:

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

เนื่องจากสิ่งเดียวที่ฉันทำคือการตั้งค่าการมุ่งเน้นไปที่องค์ประกอบฉันไม่จำเป็นต้องกังวลเกี่ยวกับตัวเองด้วยการตรวจจับการเปลี่ยนแปลงดังนั้นฉันจึงสามารถโทรไปที่renderer.selectRootElementด้านนอกของ Angular ได้ เนื่องจากฉันต้องการให้เวลาส่วนใหม่ในการเรนเดอร์ส่วนอิลิเมนต์จะถูกรวมไว้ในไทม์เอาต์เพื่อให้เวลาเธรดการเรนเดอร์ได้ทันก่อนที่จะพยายามเลือกองค์ประกอบ เมื่อติดตั้งแล้วฉันก็สามารถเรียกองค์ประกอบโดยใช้ตัวเลือก CSS พื้นฐาน

ฉันรู้ว่าตัวอย่างนี้เกี่ยวข้องกับกิจกรรมการโฟกัสเป็นหลัก แต่มันยากสำหรับฉันที่ไม่สามารถใช้ในบริบทอื่นได้


คลาส Renderer เลิกใช้ตั้งแต่ Angular 4.3.0 angular.io/api/core/Renderer
Jamie

15

สำหรับคนที่พยายามคว้าอินสแตนซ์ขององค์ประกอบภายใน*ngIfหรือ*ngSwitchCaseคุณสามารถทำตามเคล็ดลับนี้

สร้างinitคำสั่ง

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

ส่งออกส่วนประกอบของคุณด้วยชื่อเช่น myComponent

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

ใช้เทมเพลตนี้เพื่อรับอินสแตนซ์ElementRefและMyComponent

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

ใช้รหัสนี้ใน TypeScript

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

12

นำเข้าViewChildมัณฑนากรจาก@angular/coreเช่น:

รหัส HTML:

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

รหัส TS:

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

class TemplateFormComponent {

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

ตอนนี้คุณสามารถใช้วัตถุ 'myForm' เพื่อเข้าถึงองค์ประกอบใด ๆ ที่อยู่ในชั้นเรียน

Source


แต่คุณควรสังเกตว่าคุณไม่จำเป็นต้องเข้าถึงองค์ประกอบแม่แบบในคลาสองค์ประกอบคุณเพียงแค่ต้องเข้าใจตรรกะเชิงมุมอย่างถูกต้อง
Hany

3
อย่าใช้สิ่งใด ๆ เลยประเภทคือ 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
โปรดให้คำอธิบายเกี่ยวกับรหัสนี้ การโค๊ดโค้ดโดยไม่มีคำอธิบายนั้นง่ายมาก
rayryeng

5
สิ่งนี้จะไม่ทำงาน: แอตทริบิวต์ที่ตั้งค่าผ่านการเพิ่มความคิดเห็น @ViewChild จะสามารถใช้ได้หลังจากเหตุการณ์รอบอายุ ngAfterViewInit เท่านั้น การเข้าถึงค่าในตัวสร้างจะให้ค่าที่ไม่ได้กำหนดสำหรับinputTxtในกรณีนั้น
David M.

เมื่อใช้อ่าง 7 สิ่งนี้ใช้ได้อย่างไร้ที่ติ
MizAkita

5

หมายเหตุ : นี้ไม่ได้นำไปใช้กับเชิงมุม 6 ขึ้นไปเป็นElementRefกลายเป็นElementRef<T>กับTแสดงถึงประเภทของnativeElementแสดงถึงประเภทของ

ฉันต้องการเพิ่มว่าถ้าคุณใช้ElementRefตามคำแนะนำทั้งหมดแล้วคุณจะพบปัญหาทันทีที่ElementRefมีการประกาศประเภทอันยิ่งใหญ่ที่ดูเหมือนว่า

export declare class ElementRef {
  nativeElement: any;
}

นี้คือโง่ในสภาพแวดล้อมของเบราว์เซอร์ที่ nativeElement HTMLElementเป็น

เพื่อแก้ปัญหานี้คุณสามารถใช้เทคนิคต่อไปนี้

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

interface ElementRef {
  nativeElement: HTMLElement;
}

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

1
สิ่งนี้อธิบายถึงปัญหาที่ฉันมี นี้ไม่ได้ทำงานเพราะมันจะบอกitemความต้องการที่จะเป็น ElementRef แม้ว่าคุณจะตั้งค่าให้ ElementRef let item:ElementRef, item2:ElementRef; item = item2; // no can do.อื่น: สับสนมาก แต่นี่ก็ใช้ได้: let item:ElementRef, item2:ElementRef; item = item2.nativeElementเนื่องจากการใช้งานคุณชี้ให้เห็น
oooyaya

1
ที่จริงตัวอย่างแรกของคุณlet item: ElementRef, item2: ElementRef; item = item2ล้มเหลวเนื่องจากการวิเคราะห์การมอบหมายที่ชัดเจน ครั้งที่สองของคุณล้มเหลวด้วยเหตุผลเดียวกัน แต่ทั้งคู่ประสบความสำเร็จหากitem2เริ่มต้นด้วยเหตุผลที่กล่าวถึง (หรือเป็นการตรวจสอบด่วนที่มีประโยชน์สำหรับการมอบหมายที่เราสามารถใช้ได้declare letที่นี่) ไม่ว่าจะเป็นเรื่องน่าละอายที่จะเห็นanyAPI สาธารณะเช่นนี้
Aluan Haddad

2

เพื่อให้ได้พี่น้องคนต่อไปทันทีให้ใช้สิ่งนี้

event.source._elementRef.nativeElement.nextElementSibling

1

การเลือกองค์ประกอบเป้าหมายจากรายการ มันง่ายต่อการเลือกองค์ประกอบเฉพาะจากรายการองค์ประกอบเดียวกัน

รหัสส่วนประกอบ:

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:

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

รหัส css:

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

0

ตัวอย่างน้อยที่สุดสำหรับการใช้งานอย่างรวดเร็ว:

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. วางตัวแปรอ้างอิงเทมเพลตในองค์ประกอบ DOM ที่น่าสนใจ ในตัวอย่างของเรานี่คือ#inputElบน<input>แท็ก
  2. ในชั้นองค์ประกอบของเราฉีดองค์ประกอบ DOM ผ่านทาง @ViewChild มัณฑนากร
  3. เข้าถึงองค์ประกอบในngAfterViewInitlifecycle hook

บันทึก:

หากคุณต้องการจัดการองค์ประกอบ DOM ให้ใช้ Renderer2 API แทนการเข้าถึงองค์ประกอบโดยตรง การอนุญาตให้เข้าถึงโดยตรงไปยัง DOM สามารถทำให้แอปพลิเคชันของคุณเสี่ยงต่อการถูกโจมตี XSS

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.