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

355

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

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

Теги:
angular
angular-components
angular2-components

10 ответов

703
Лучший ответ

Вместо того, чтобы вводить 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/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);
  }
}

получить произвольный прогнозируемый контент

См. Доступ к удаленному контенту

  • 11
    Угловые команды не рекомендовали использовать ElementRef, это лучшее решение.
  • 6
    Фактически input также является ElementRef , но вы получаете ссылку на элемент, который вам действительно нужен, вместо того, чтобы запрашивать его у хоста ElementRef .
Показать ещё 17 комментариев
169

Вы можете получить дескриптор элемента DOM через ElementRef, вставив его в свой конструктор компонента:

constructor(myElement: ElementRef) { ... }

Документы: https://angular.io/docs/ts/latest/api/core/index/ElementRef-class.html

  • 1
    @Brocco можешь ли ты обновить свой ответ? Я хотел бы видеть текущее решение, так как ElementRef ушел.
  • 23
    ElementRef доступен (снова?).
Показать ещё 5 комментариев
42
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) {
    // ...
  }
}

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

Подробнее о собственном элементе здесь

15

Angular 4 +: Используйте renderer.selectRootElement с помощью селектора CSS для доступа к элементу.

У меня есть форма, которая первоначально отображает ввод электронной почты. После ввода электронной почты форма будет расширена, чтобы они могли продолжать добавлять информацию, относящуюся к их проекту. Однако, если они являются не существующим клиентом, форма будет содержать адресный раздел над секцией информации о проекте.

На данный момент часть ввода данных не была разбита на компоненты, поэтому секции управляются с помощью директив * ngIf. Мне нужно установить фокус на поле проектных заметок, если они являются существующим клиентом или поле имени, если они новы.

Я пробовал решения без успеха. Однако Update 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.

Я знаю, что этот пример касается в основном фокусного события, но мне трудно, что это нельзя использовать в других контекстах.

12

Для людей, пытающихся захватить экземпляр компонента внутри *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) {
}
7

импортируйте декоратор ViewChild из @angular/core, например:

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



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

    class TemplateFormComponent {

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

теперь вы можете использовать объект myForm для доступа к любому элементу внутри него в классе.

источник: https://codecraft.tv/courses/angular/forms/template-driven/

  • 0
    Но вы должны заметить, что вам почти не нужен доступ к элементам шаблона в классе компонентов, вам просто нужно правильно понимать угловую логику.
  • 2
    Не используйте, тип ElementRef
6
 */
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*/
        };
    }
}
  • 6
    Пожалуйста, предоставьте некоторые пояснения к этому коду. Просто сброс кода без объяснения причин не рекомендуется.
  • 3
    Это не будет работать: атрибуты, установленные с помощью аннотаций @ViewChild, будут доступны только после события жизненного цикла ngAfterViewInit. В этом случае inputTxt к значению в конструкторе приведет к неопределенному значению для inputTxt .
3

Я хотел бы добавить, что если вы используете 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 как этот.
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;
}
1

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

event.source._elementRef.nativeElement.nextElementSibling

Ещё вопросы

Сообщество Overcoder
Наверх
Меню