Як я можу вибрати елемент у шаблоні компонента?


516

Хтось знає, як влаштувати елемент, визначений у шаблоні компонента? Полімер робить це дуже легко за допомогою $та $$.

Мені було просто цікаво, як це зробити в Angular.

Візьміть приклад з підручника:

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;

підписатися на зміни

Навіть незважаючи на те, що діти перегляду встановлюються лише тоді, коли ngAfterViewInit()викликається, а вміст дітей встановлюється лише тоді, коли ngAfterContentInit()викликається, якщо ви хочете підписатися на зміни результату запиту, це слід зробити вngOnInit()

https://github.com/angular/angular/isissue/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, це краще рішення.
Почесний Чоу

7
Насправді inputтакож є ElementRef, але ви отримуєте посилання на елемент, який ви насправді хочете, замість того, щоб запитувати його від хоста ElementRef.
Günter Zöchbauer

32
Насправді використовувати ElementRefпросто чудово. Також використання ElementRef.nativeElementз Renderer- це добре. Те , що відлякує, це доступ до властивостей ElementRef.nativeElement.xxxбезпосередньо.
Günter Zöchbauer

2
@Natanael Я не знаю, чи це чітко зафіксовано, але в питаннях чи інших дискусіях (також від членів групи Angular) регулярно згадується, що слід уникати прямого доступу до дому. Доступ до 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ünter Zöchbauer

10
Посилання Використовуйте цей API як крайній захід, коли потрібен прямий доступ до DOM. Використовуйте шаблони та прив'язку даних, які надає Angular замість цього. Крім того, ви можете подивитися на Renderer, який надає API, який можна безпечно використовувати, навіть якщо не підтримується прямий доступ до нативних елементів. Опираючись на прямий доступ до дому, створює тісний зв'язок між вашим додатком та шарами візуалізації, що унеможливить їх розділення та розгортання вашої програми у веб-службовця.
sandeep talabathula

@sandeeptalabathula Що є кращим варіантом пошуку елемента, до якого можна приєднати компонент вибору плаваючої дати з сторонньої бібліотеки? Я знаю, що це було не первісне питання, але ви робите висновок, що пошук елементів у DOM поганий у всіх сценаріях ...
Джон

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

Кутова 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 ВИМОГАЄТЬСЯ з кутових 4.3.0. angular.io/api/core/Renderer
Джеймі

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 { ... }

Використовуйте цей шаблон, щоб отримати екземпляр ElementRefANDMyComponent

<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


Але ви повинні помітити, що вам майже не потрібно звертатися до елементів шаблонів у класі компонентів, вам просто потрібно добре зрозуміти кутову логіку.
Хані

3
Не використовуйте жодного, тип - ElementRef
Йоганнес

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цього випадку.
Девід М.

Використовуючи ang 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тут). Незважаючи на це, справді соромно бачити anyтакий публічний API.
Алуан Хаддад

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. Доступ до елемента в ngAfterViewInitгачку життєвого циклу.

Примітка:

Якщо ви хочете маніпулювати елементами DOM, використовуйте API Renderer2 замість прямого доступу до елементів. Дозвіл прямого доступу до DOM може зробити вашу програму більш вразливою до XSS-атак

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.