16

I am using *ngFor to create a bunch of divs. I would like to get my hands on one of them and get its width.

.html

<div #elReference *ngFor="let el of elements> HEHE </div>

.ts

@ViewChild("elReference") elReference: ElementRef;

Apparently, you cannot use ViewChild with *ngFor, because elReference remains undefined.

How do you get element reference created with *ngFor?

Chris Barr
  • 29,851
  • 23
  • 95
  • 135
sanjihan
  • 5,592
  • 11
  • 54
  • 119
  • Take a look at this thread: https://stackoverflow.com/questions/40165294/access-multiple-viewchildren-using-viewchild – Christian Benseler Oct 31 '17 at 11:18
  • Thanks. I tried @ViewChildren("elReference") elReferences: QueryList; , but still getting an undefined value for elReferences. – sanjihan Oct 31 '17 at 11:38
  • I used it in onInit(). it appears ngAfterViewInit is mandatory. – sanjihan Oct 31 '17 at 11:45
  • 1
    You have to use ngAfterViewInit because you must wait the template to be parsed and the DOM mounted. – Christian Benseler Oct 31 '17 at 11:49
  • Thanks. I am indeed getting the clientWidth now. For some reason, it is rounded to nearest integer. This code console.log(this. elReference.nativeElement.clientWidth); returns 25, although the actual width in browser is 25.11. $("element").outerwidth() also returns 25.11 – sanjihan Oct 31 '17 at 12:07

3 Answers3

12

There is no way to add a template variable selectively, but you can add a marker selectively and then filter by that:

<div #elReference [attr.foo]="el.x == 'foo' ? true : null" *ngFor="let el of elements"> HEHE </div>
@ViewChildren("elReference") elReference: QueryList<ElementRef>;

ngAfterViewInit() {
  console.log(this.elReference.toArray()
      .filter(r => r.nativeElement.hasAttribute('foo')));
}
Chris Barr
  • 29,851
  • 23
  • 95
  • 135
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
5

There is a more complicated, but more correct way with a directive.

<!-- app.component.html -->
<div *ngFor="let el of elements" [id]="el.id"> HEHE </div>
// div-id.directive.ts
import { Directive, Input, ElementRef } from '@angular/core';

@Directive({
  selector: 'div[id]',
})
export class DivIdDirective {
  @Input() id: number;

  constructor(ref: ElementRef<HTMLDivElement>) {
    this.el = ref.nativeElement;
  }

  el: HTMLDivElement;
}

// app.component.ts
export class AppComponent implements AfterViewInit {
  // ...
  @ViewChildren(DivIdDirective) els: QueryList<DivIdDirective>;

  ngAfterViewInit(): void {
    console.log(this.els.map(({id, el}) => ({id, el}));
  }
}

Eugene P.
  • 1,517
  • 12
  • 16
0

An simple and elegant approach is to leverage the component model of Angular. Move the content of the iterated element into a component.

Instead of

<div ngFor="let a of aList">
  <div #a>{{a}}</div>
</div>

You would have

<div ngFor="let a of aList">
  <app-a [a]="a"></app-a>
</div>

with app-a being

<div #a>{{a}}</div>
Octave
  • 351
  • 2
  • 11