Comment puis-je sélectionner un élément dans un modèle de composant?

517

Quelqu'un sait-il comment mettre la main sur un élément défini dans un modèle de composant? Le polymère le rend vraiment facile avec $et $$.

Je me demandais juste comment m'y prendre dans Angular.

Prenons l'exemple du tutoriel:

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

Comment puis-je intercepter ou obtenir une référence de l' élément pou à inputpartir de la définition de classe?

Aman Gupta
la source

Réponses:

939

Au lieu d'injecter ElementRefet d'utiliser querySelectorou similaire à partir de là, un moyen déclaratif peut être utilisé à la place pour accéder directement aux éléments de la vue:

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

élément

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

Exemple StackBlitz

  • @ViewChild () prend en charge la directive ou le type de composant comme paramètre, ou le nom (chaîne) d'une variable de modèle.
  • @ViewChildren () prend également en charge une liste de noms en tant que liste séparée par des virgules (actuellement aucun espace n'est autorisé @ViewChildren('var1,var2,var3')).
  • @ContentChild () et @ContentChildren () font de même mais dans le DOM léger ( <ng-content>éléments projetés).

descendance

@ContentChildren() est le seul qui permet également d'interroger les descendants

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

{descendants: true}devrait être la valeur par défaut mais n'est pas dans la version 2.0.0 finale et il est considéré comme un bug
Cela a été corrigé dans la version 2.0.1

lis

S'il existe un composant et des directives, le readparamètre permet de spécifier quelle instance doit être retournée.

Par exemple, ce ViewContainerRefqui est requis par les composants créés dynamiquement au lieu de la valeur par défautElementRef

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

abonner les modifications

Même si les enfants de vue ne sont définis que lorsque ngAfterViewInit()est appelé et les enfants de contenu ne sont définis que lorsque ngAfterContentInit()est appelé, si vous souhaitez vous abonner aux modifications du résultat de la requête, cela doit être fait dansngOnInit()

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

accès direct au DOM

peut uniquement interroger des éléments DOM, mais pas des composants ou des instances de directive:

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

obtenir un contenu projeté arbitraire

Voir Accéder au contenu transclus

Günter Zöchbauer
la source
12
Les équipes angulaires déconseillent d'utiliser ElementRef, c'est la meilleure solution.
Honorable Chow
7
En fait, c'est inputaussi un ElementRef, mais vous obtenez la référence à l'élément que vous voulez réellement, au lieu de l'interroger à partir de l'hôte ElementRef.
Günter Zöchbauer
32
En fait, ElementRefc'est très bien. L'utilisation ElementRef.nativeElementavec Rendererest également très bien. Ce qui est déconseillé, c'est d'accéder ElementRef.nativeElement.xxxdirectement aux propriétés de .
Günter Zöchbauer
2
@Natanael Je ne sais pas si ni où cela est explicitement documenté, mais il est mentionné régulièrement dans des problèmes ou d'autres discussions (également par les membres de l'équipe angulaire) que l'accès direct au DOM doit être évité. Accéder directement au DOM (ce qui est l'accès aux propriétés et aux méthodes ElementRef.nativeElement), vous empêche d'utiliser le rendu côté serveur Angulars et la fonctionnalité WebWorker (je ne sais pas si cela casse également le compilateur de modèles hors ligne à venir - mais je suppose que non).
Günter Zöchbauer
10
Comme mentionné ci-dessus dans la section de lecture , si vous voulez obtenir le nativeElement pour un élément avec ViewChild, vous devez faire ce qui suit: @ViewChild('myObj', { read: ElementRef }) myObj: ElementRef;
jsgoupil
203

Vous pouvez obtenir un handle vers l'élément DOM via ElementRefen l'injectant dans le constructeur de votre composant:

constructor(myElement: ElementRef) { ... }

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

Brocco
la source
1
@Brocco pouvez-vous mettre à jour votre réponse? J'aimerais voir une solution actuelle depuis qu'il ElementRefest parti.
Jefftopia
23
ElementRefest disponible (encore?).
Günter Zöchbauer
10
link Utilisez cette API en dernier recours lorsqu'un accès direct au DOM est nécessaire. Utilisez plutôt les modèles et la liaison de données fournis par Angular. Vous pouvez également jeter un œil à Renderer qui fournit une API qui peut être utilisée en toute sécurité même lorsque l'accès direct aux éléments natifs n'est pas pris en charge. Le fait de s'appuyer sur l'accès direct au DOM crée un couplage étroit entre votre application et les couches de rendu, ce qui rendra impossible la séparation des deux et le déploiement de votre application dans un travailleur Web.
sandeep talabathula
@sandeeptalabathula Quelle est la meilleure option pour trouver un élément auquel attacher un composant de sélecteur de date flottant à partir d'une bibliothèque tierce? Je suis conscient que ce n'était pas la question d'origine, mais vous faites comprendre que trouver des éléments dans le DOM est mauvais dans tous les scénarios ...
John
13
@john Ah .. d'accord. Vous pouvez essayer ceci - this.element.nativeElement.querySelector('#someElementId')et passer ElementRef au constructeur comme ceci .. private element: ElementRef, Importer la 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) {
    // ...
  }
}

Exemple mis à jour pour fonctionner avec la dernière version

Pour plus de détails sur l'élément natif, ici

gdi2290
la source
20

Angular 4+ : utilisationrenderer.selectRootElement avec un sélecteur CSS pour accéder à l'élément.

J'ai un formulaire qui affiche initialement une entrée e-mail. Une fois l'e-mail entré, le formulaire sera développé pour leur permettre de continuer à ajouter des informations relatives à leur projet. Cependant, s’ils ne le sont pas un client existant, le formulaire comprendra une section d'adresse au-dessus de la section d'informations sur le projet.

Pour l'instant, la partie saisie de données n'a pas été divisée en composants, les sections sont donc gérées avec les directives * ngIf. Je dois mettre l'accent sur le champ des notes du projet s'il s'agit d'un client existant ou sur le champ du prénom s'il est nouveau.

J'ai essayé les solutions sans succès. Cependant, la mise à jour 3 de cette réponse m'a donné la moitié de la solution finale. L'autre moitié provient de la réponse de MatteoNY dans ce fil. Le résultat est le suivant:

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

Étant donné que la seule chose que je fais est de mettre l'accent sur un élément, je n'ai pas besoin de me préoccuper de la détection des modifications, donc je peux réellement lancer l'appel à renderer.selectRootElement extérieur d'Angular. Étant donné que je dois donner le temps aux nouvelles sections de s'afficher, la section de l'élément est enveloppée dans un délai d'expiration pour permettre aux threads de rendu de rattraper le temps avant que la sélection de l'élément ne soit tentée. Une fois tout cela configuré, je peux simplement appeler l'élément en utilisant des sélecteurs CSS de base.

Je sais que cet exemple concerne principalement l'événement focus, mais il est difficile pour moi que cela ne puisse pas être utilisé dans d'autres contextes.

Neil T.
la source
La classe Renderer est obsolète depuis Angular 4.3.0. angular.io/api/core/Renderer
Jamie
15

Pour les personnes essayant de saisir l'instance de composant dans un *ngIfou *ngSwitchCase, vous pouvez suivre cette astuce.

Créez une initdirective.

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

Exportez votre composant sous un nom tel que myComponent

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

Utilisez ce modèle pour obtenir l' instance ElementRefANDMyComponent

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

Utilisez ce code dans TypeScript

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

importer le ViewChilddécorateur de@angular/core , comme ceci:

Code HTML:

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

Code TS:

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

class TemplateFormComponent {

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

vous pouvez maintenant utiliser l'objet 'myForm' pour accéder à n'importe quel élément qu'il contient dans la classe.

Source

Hany
la source
Mais vous devez noter que vous n'avez presque pas besoin d'accéder aux éléments de modèle dans la classe de composants, il vous suffit de bien comprendre la logique angulaire correctement.
Hany
3
N'en utilisez pas, le type est 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
la source
10
Veuillez fournir des explications sur ce code. Le simple dumping de code sans explication est fortement déconseillé.
rayryeng
5
Cela ne fonctionnera pas: les attributs définis via les annotations @ViewChild ne seront disponibles qu'après l'événement de cycle de vie ngAfterViewInit. L'accès à la valeur dans le constructeur produirait une valeur indéfinie pour inputTxtdans ce cas.
David M.
En utilisant ang 7, cela a fonctionné parfaitement.
MizAkita
5

Remarque : Cela ne s'applique pas à Angular 6 et supérieur, comme celaElementRefest devenuElementRef<T>enTindiquant le type denativeElement.

Je voudrais ajouter que si vous utilisez ElementRef, comme recommandé par toutes les réponses, vous rencontrerez immédiatement le problème qui ElementRefa une déclaration de type horrible qui ressemble à

export declare class ElementRef {
  nativeElement: any;
}

c'est stupide dans un environnement de navigateur où nativeElement est un HTMLElement.

Pour contourner ce problème, vous pouvez utiliser la technique suivante

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

interface ElementRef {
  nativeElement: HTMLElement;
}

@Component({...}) export class MyComponent {
  constructor(@Inject(ErrorProneElementRef) readonly elementRef: ElementRef) { }
}
Aluan Haddad
la source
1
Cela explique un problème que j'avais. Cela ne fonctionne pas parce qu'il va dire itemdoit être un , élément d' , même si vous configurez à un autre , élément d' : let item:ElementRef, item2:ElementRef; item = item2; // no can do.. Très perturbant. Mais c'est très bien: à let item:ElementRef, item2:ElementRef; item = item2.nativeElementcause de l'implémentation que vous avez soulignée.
oooyaya
1
En fait, votre premier exemple let item: ElementRef, item2: ElementRef; item = item2échoue en raison d'une analyse d'affectation définitive. Votre deuxième échoue pour les mêmes raisons, mais les deux réussissent s'il item2est initialisé pour les raisons évoquées (ou en tant que vérification rapide de l'affectation que nous pouvons utiliser declare letici). Quoi qu'il en soit, vraiment dommage de voir anysur une API publique comme celle-ci.
Aluan Haddad
2

pour obtenir le prochain frère immédiat, utilisez ceci

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

Sélection de l'élément cible dans la liste. Il est facile de sélectionner un élément particulier dans la liste des mêmes éléments.

code composant:

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

Code HTML:

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

code CSS:

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

Exemple minimal pour une utilisation rapide:

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. Mettez une variable de référence de modèle sur l'élément DOM d'intérêt. Dans notre exemple, c'est le #inputElsur l' <input>étiquette.
  2. Dans notre classe de composants, injectez l'élément DOM via le décorateur @ViewChild
  3. Accédez à l'élément dans le ngAfterViewInitcrochet du cycle de vie.

Remarque:

Si vous souhaitez manipuler les éléments DOM, utilisez l'API Renderer2 au lieu d'accéder directement aux éléments. Autoriser l'accès direct au DOM peut rendre votre application plus vulnérable aux attaques XSS

Willem van der Veen
la source