2

I want to update the UI of a component after it's fully rendered. Because it's rendering elements in a for, my understanding is that I need to use a subscription to check for the elements to first be created before I interact with the UI.

Based on examples I've read, this is what I came up with. However, nothing happens in that my console.log statement inside my subscription never fires. No errors. It's as if my subscription doesn't see any changes. Is there anything obviously missing from my logic here?

template markup:

<ng-container *ngFor="let item of items; let i = index;">
    <input *ngIf="..." #inputItem>

Angular (5):

@ViewChildren('inputItem', {read: ViewContainerRef }) inputItems: QueryList<ViewContainerRef>;

ngAfterViewInit(): any {
    this.inputItems.changes.subscribe(() => {
        console.log('items created');
    });
}
DA.
  • 39,848
  • 49
  • 150
  • 213
  • 1
    Why don't you use `@ViewChildren("inputItem") inputItems: QueryList;`? The code is shown in [this answer](https://stackoverflow.com/a/49227093/1009922), which was referred to in your [previous question](https://stackoverflow.com/q/51543229/1009922). – ConnorsFan Jul 26 '18 at 18:18
  • Possible duplicate of [Retrieve elements created by ngFor in ngAfterViewInit](https://stackoverflow.com/questions/49226233/retrieve-elements-created-by-ngfor-in-ngafterviewinit) – ConnorsFan Jul 26 '18 at 18:20
  • @ConnorsFan mainly because none of this is my code. Trying to modify something that already exist. Let me investigate why ViewContainerRef vs. ElementRef was used. Are observables only usable with one of those? – DA. Jul 26 '18 at 18:21

1 Answers1

2

It worked fine for me with a few minor changes. This is what I ended up with:

Template

<tr *ngFor='let product of filteredProducts' >
  <input *ngIf='product.imageUrl' #inputItem type='text'>

Component

import { Component, OnInit, ViewChildren, 
         QueryList, AfterViewInit, ElementRef } from '@angular/core';

@ViewChildren('inputItem') inputItems: QueryList<ElementRef>

  ngAfterViewInit(): void {
    this.inputItems.changes.subscribe(value => {
      console.log(value);
      this.inputItems.map((item => item.nativeElement.style.backgroundColor = 'red') )
    }
    );
  }

UPDATE 8/8/19 RE: Unsubscribing:

Technically, you should not have to unsubscribe from any Observables defined on an element as they will be destroyed when the form is destroyed.

However, developers in general have been moving to a more explicit approach and been using the rule that if you subscribe you should always unsubscribe.

In the apps I work on now, I use the async pipe exclusively (as shown here: https://github.com/DeborahK/Angular-RxJS) and therefore have no subscribes (and no unsubscribes).

If you did want to unsubscribe from the above, you would first need to put the subscription into a class variable:

this.sub = this.inputItems.changes.subscribe(...);

Then in the destroy:

this.sub.unsubscribe();
DeborahK
  • 57,520
  • 12
  • 104
  • 129
  • I'm not clear on what syntax I had wrong. :) Is this issue ViewContainerRef vs ElementRef? – DA. Jul 26 '18 at 18:26
  • That and there is an extra parenthesis at the end of the ViewChildren and a space between the = and > for the arrow function. And the return type from the method should be void (not any). – DeborahK Jul 26 '18 at 18:27
  • ha! Ooops. I'm a sloppy typer. Fixed those above. So yes, it appears the issue is that I can not subscribe to changes with a ViewContainerRef. Which is...a bummer...as I can't change that. Going to have to figure out an alternative solution. Thanks! – DA. Jul 26 '18 at 18:30
  • 1
    If you want to post a stackblitz with a *small* example demonstrating what you are trying to achieve, we can look at it further. – DeborahK Jul 26 '18 at 18:31
  • @DeborahK don't you have to unsubscribe from the subscriptions when the component is destroy? If yes, how? – ps0604 Aug 08 '19 at 17:31