Как выбрать элемент в шаблоне компонента?

517

Кто-нибудь знает, как получить элемент, определенный в шаблоне компонента? Полимер делает это действительно легко с $и $$.

Мне было просто интересно, как это сделать в 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из определения класса?

Аман Гупта
источник

Ответы:

939

Вместо того, чтобы вводить 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 final, и это считается ошибкой.
Это было исправлено в 2.0.1.

читать

Если есть компонент и директивы, readпараметр позволяет указать, какой экземпляр должен быть возвращен.

Например ViewContainerRef, это требуется для динамически создаваемых компонентов, а не по умолчаниюElementRef

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

подписаться на изменения

Даже если дочерние элементы представления устанавливаются только при ngAfterViewInit()вызове, а дочерние элементы содержимого устанавливаются только при 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, это лучшее решение.
Почетное чау-чау
7
На самом деле inputтакже есть ElementRef, но вы получаете ссылку на элемент, который вам действительно нужен, вместо того, чтобы запрашивать его у хоста ElementRef.
Гюнтер Цохбауэр
32
На самом деле использование ElementRefпросто отлично. Также использование ElementRef.nativeElementс Rendererхорошо. Что обескураживает, так это ElementRef.nativeElement.xxxпрямой доступ к свойствам .
Гюнтер Цохбауэр
2
@Natanael Я не знаю, если или где это явно задокументировано, но в вопросах или других обсуждениях (также от членов команды Angular) регулярно упоминается, что прямого доступа к DOM следует избегать. ElementRef.nativeElement)Прямой доступ к DOM (то есть доступ к свойствам и методам не позволяет вам использовать рендеринг на стороне сервера Angulars и функцию WebWorker (я не знаю, нарушает ли это также готовящийся к выходу компилятор шаблонов в автономном режиме - но я полагаю, что нет).
Гюнтер 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

Brocco
источник
1
@Brocco можешь ли ты обновить свой ответ? Я хотел бы видеть текущее решение, так ElementRefкак ушел.
Джеффтопия
23
ElementRefдоступно (опять?).
Гюнтер Цохбауэр
10
ссылка Используйте этот API в качестве последнего средства, когда требуется прямой доступ к DOM. Вместо этого используйте шаблоны и привязку данных, предоставляемые Angular. В качестве альтернативы вы посмотрите на Renderer, который предоставляет API, который можно безопасно использовать, даже если прямой доступ к собственным элементам не поддерживается. Использование прямого доступа к DOM создает тесную связь между вашим приложением и уровнями рендеринга, что делает невозможным их разделение и развертывание приложения в веб-работнике.
sandeep talabathula
@sandeeptalabathula Как лучше всего найти элемент для присоединения компонента средства выбора даты с плавающей точкой из сторонней библиотеки? Я знаю, что это не был первоначальный вопрос, но вы понимаете, что поиск элементов в DOM плох во всех сценариях ...
Джон
13
@ Джон А, ладно. Вы можете попробовать это - this.element.nativeElement.querySelector('#someElementId')и передать ElementRef в конструктор следующим образом. private element: ElementRef, Импортировать import { ElementRef } from '@angular/core';
библиотеку
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) {
    // ...
  }
}

Обновлен пример для работы с последней версией

Для более подробной информации о нативном элементе, здесь

gdi2290
источник
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
Джейми
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) {
}
jsgoupil
источник
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*/
        };
    }
}
Eng.Gabr
источник
10
Пожалуйста, предоставьте некоторые пояснения к этому коду. Просто сброс кода без объяснения причин не рекомендуется.
Райрюнг
5
Это не будет работать: атрибуты, установленные с помощью аннотаций @ViewChild, будут доступны только после события жизненного цикла ngAfterViewInit. В этом случае доступ к значению в конструкторе приведет к неопределенному значению inputTxt.
Дэвид М.
Используя анг 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из-за реализации, которую вы указали.
ооооя
1
На самом деле ваш первый пример let item: ElementRef, item2: ElementRef; item = item2терпит неудачу из-за анализа определенного присваивания. Ваш второй сбой по тем же причинам, но оба успешны, если item2инициализируются по рассмотренным причинам (или в качестве полезной быстрой проверки назначаемости, которую мы можем использовать declare letздесь) Независимо от того, действительно стыдно видеть anyна публичном API, как это.
Алуан Хаддад
2

чтобы получить ближайшего родственника, используйте это

event.source._elementRef.nativeElement.nextElementSibling
Apoorv
источник
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

Виллем ван дер Веен
источник