Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ViewChildren doesn't bind in ngFor

I have a simple template:

<div *ngFor="let skill of allSkills | async ">
    <div class="skill-chart-with-img">
        <skill-chart-view></skill-chart-view>
        <div #skillImg>
            some data
        </div>
    </div>
</div>

When I try to bind on #skillImg, I have an empty QueryList:

@ViewChildren("skillImg") private skillImgs: QueryList<ElementRef>;
ngAfterViewInit(): void {
        this.skillImgs.forEach((skill: ElementRef) => {
           //some work with skill
        });
}

Maybe I miss something or my task have a better solution?

like image 790
Illorian Avatar asked Aug 15 '16 07:08

Illorian


People also ask

What is the difference between ViewChild and ViewChildren?

Another critical difference is that @ViewChild returns a single native DOM element as a reference, while the @ViewChildren decorator returns the list of different native DOM elements in the form of QueryList , which contains the set of elements.

Why is ViewChild returning undefined?

So, when the component is initialized the component is not yet displayed until "showMe" is true. Thus, my @ViewChild references were all undefined. This is where I used @ViewChildren and the QueryList that it returns. See angular article on QueryList and a @ViewChildren usage demo.

Why do we use @ViewChild in angular2?

The @ViewChild decorator allows us to inject into a component class references to elements used inside its template, that's what we should use it for. Using @ViewChild we can easily inject components, directives or plain DOM elements.

What is Angular ViewChildren?

ViewChildlinkProperty decorator that configures a view query. The change detector looks for the first element or the directive matching the selector in the view DOM. If the view DOM changes, and a new child matches the selector, the property is updated.


3 Answers

The problem was in async data. The solution is to subscribe on QueryList changes

Example:

ngAfterViewInit(): void {
    this.skillImgs.changes
        .subscribe(() => console.log(this.skillImgs));
}
like image 59
Illorian Avatar answered Oct 11 '22 20:10

Illorian


NOTE: You have to check whether async works properly or not. Because its hard to identify any problem if associated with async pipe. But other than it, ViewChildren works with *ngFor as shown below,

export class App {

  @ViewChildren("skillImg") private skillImgs: QueryList<ElementRef>;

  constructor(private renderer: Renderer) {}


    ngAfterViewInit(): void {
            this.skillImgs.forEach((skill: ElementRef) => {
                 this.renderer.setElementStyle(skill.nativeElement,"background","yellow");    
            });
    }

}

https://plnkr.co/edit/pI35tx9gXZFO1sXj9Obm?p=preview

like image 43
Nikhil Shah Avatar answered Oct 11 '22 20:10

Nikhil Shah


This issue only occurs on the dev mode. There are a number of ways by which you can handle this.

  1. Explicit change detection
constructor(private changeDetector: ChangeDetectorRef) {
}

ngAfterViewInit() {
  this.skillImgs.changes.subscribe(() => {
    //make changes here
    this.changeDetector.detectChanges();
  })
}

  1. Use Promise
    ngAfterViewInit() {
        this.skillImgs.changes.subscribe(()=>{
            Promise.resolve().then(()=>{
               //make changes here
            });
         })
     }
  1. Timeout
    ngAfterViewInit() {
    this.skillImgs.changes.subscribe(()=>{
      settimeout(()=>{
        //make changes here
      });
    }, 0)’
  }

Reference from here

like image 34
umunBeing Avatar answered Oct 11 '22 22:10

umunBeing