31

For some reason my @ViewChild in my Angular 5 App does not work. I have defined it like this in my component:

case-detail.component.html:

<div class="inner-tab-content" #innerTabContent>
    <!-- // more content here -->
</div>

I have implemented @ViewChild in my controller (above the constructor) like this:

case-detail.component.ts

@ViewChild('innerTabContent') tabContentElement: ElementRef;

And I want to access it here in the component: case-detail.component.ts

ngAfterViewInit() {
    console.log("scroll top: " + this.tabContentElement.nativeElement);
}

I've implemented the AfterViewInit interface. ngAfterViewInit() is called correctly. However, this.tabContentElement is always undefined.

Any help is greatly appreciated :)

pjpscriv
  • 866
  • 11
  • 20
dave0688
  • 5,372
  • 7
  • 33
  • 64
  • Can you show us how have you implemented @ViewChild? – Vinod Bhavnani Jan 18 '18 at 12:38
  • Sure, just updated my answer :) – dave0688 Jan 18 '18 at 12:40
  • 1
    if you are using `#innerTabContent` you will have the elementRef directly and not `nativeElement`. `console.log(this.tabContentElement)` you will get the HTML element – Aravind Jan 18 '18 at 12:44
  • @Aravind That does not work either. this.tabContentElement is undefined unfortunately. – dave0688 Jan 18 '18 at 12:48
  • 2
    @dave0688 Is it possible that the block where your #innerTabContent element is defined is in some `*ngIf` or other conditional template that is not rendered at the time of afterviewinit ? – Pac0 Jan 18 '18 at 12:48
  • Can you try putting the read property? @ViewChild('innerTabContent', {read: ElementRef}) tabContentElement: ElementRef; – Vinod Bhavnani Jan 18 '18 at 12:52
  • @Pac0 Good guess. Yes, that's the case! :) Just tried it with a timeout of 5 seconds and it works. Thanks a lot :) Please write an answer so I can mark it as such. – dave0688 Jan 18 '18 at 12:57

6 Answers6

26

ViewChild() works fine on latest plunker Angular version with the scenario you describe.

Demonstration in this plunker : https://plnkr.co /edit/KzWnkE5Hvp7NUow6YAxy

EDIT: Here is a replacement StackBlitz for the above Plunker: https://stackblitz.com/edit/angular-ivy-pzaglm

component :

ngAfterViewInit() {
    console.log(this.testView); // correctly outputs the element in console, not undefined
}
  • Check that ElementRef and ViewChild are correctly imported from '@angular/core'

  • Your element might simply not be there at the time of AfterViewInit (in case there is a *ngIf, for instance. (seems the case as per your comments)

In the latter case, you can use a wrapper element and ViewChildren , that emits some event when a new child element is added - more info on documentation here : https://angular.io/api/core/ViewChildren

note that there might be some issue with native div as per this question : @ViewChildren does not get updated with dynamically added DOM elements , but this can be worked around by using a new component that wraps your div, for instance.

EDIT

Or you can also use a timeout to wait for the component to be rendered. I must say that I find this solution 'dirty', but glad it works for you :)

Pac0
  • 21,465
  • 8
  • 65
  • 74
  • 3
    to rely on timeout is a really bad idea. – hgoebl Oct 30 '18 at 15:52
  • Why does *ngIf cause the problem with elements not appearing AfterViewInigt? – IcedDante Dec 02 '19 at 16:25
  • 1
    @IcedDante Not sure if I understood your actual question properly, but if the condition is _falsy_ in `*ngIf="condition"`, then the actual element won't be present in the DOM at all. So you can't select it. That's an expected behavior of Angular. – Pac0 Dec 02 '19 at 16:32
  • oh ok... yes I thought because you said the element "might... not be there at the time" that it would asynchronously appear later for some reason. – IcedDante Dec 02 '19 at 17:42
  • 1
    If 'viewChild' element is with *ngIf block, it will return undefined only. In this case, need to replace *ngIf with [style.display]="conditionToDisplay > 0 ? 'block' : 'none'" . – JP Bala Krishna Jun 01 '22 at 14:10
19

However, even if you access to the child component in the AfterViewInit, sometimes the @ViewChild was still returning null. The problem can be caused by the *ngIf or other directive.

The solution is to use the @ViewChildren instead of @ViewChild and subscribe the changes subscription that is executed when the component is ready.

For example, if in the parent component ParentComponent you want to access the child component MyComponent.

import { Component, ViewChildren, AfterViewInit, QueryList } from '@angular/core';
import { MyComponent } from './mycomponent.component';

export class ParentComponent implements AfterViewInit
{
  //other code emitted for clarity

  @ViewChildren(MyComponent) childrenComponent: QueryList<MyComponent>;

  public ngAfterViewInit(): void
  {
    this.childrenComponent.changes.subscribe((comps: QueryList<MyComponent>) =>
    {
      // Now you can access to the child component
    });
  }
}
Marco Barbero
  • 1,460
  • 1
  • 12
  • 22
  • 4
    But what do you do if this.childrenComponent is still undefined by time you reach ngAfterViewInit -- there's nothing to subscribe to :( – Magnus Jan 15 '19 at 20:42
  • I think that you are missing something on your code, because `this.childrenComponent` is the reference of the template component. If you put your code on "CodeSandbox" I can try to help you. – Marco Barbero Jan 16 '19 at 16:28
9

To get this to work consistently in my case, I replaced all occurrences of

*ngIf="check"

with

[style.display]="check ? 'block' : 'none'"

Otherwise the ViewChild components that didn't exist when my view first loaded would potentially remain undefined.

John Langford
  • 1,272
  • 2
  • 12
  • 15
5

A different answer to this question would be sometimes, The child component is still not rendered, when you access it. For me it was due to an *ngIf so that child component is still not there. For an example,

<div *ngIf="check">
   <app-child-item></app-child-item>
</div>

And in your parent .ts file you try to access childItem while check is set to false.

dilantha111
  • 1,388
  • 1
  • 17
  • 19
0

Using timeout and setting static to false worked for me.

@ViewChild(GoogleMapComponent) _GoogleMap: GoogleMapComponent; //Static is false
        
//Now use setTimeout inside ngOnInit
ngOnInit(){
    setTimeout(() => {
      this._GoogleMap.$mapReady.subscribe((map) => {
        this.map = map;
        marker.setMap(map);
      });
    });
}
theGateway
  • 91
  • 1
  • 5
0

I have the same issue as Magnus where @ViewChildren still has a chance of being undefined during ngAfterViewInit. I've found another solution that avoids any race conditions or timers.

Change your @ViewChild variable to a set function, and store the ElementRef in a separate variable.

@ViewChild('innerTabContent')
set tabContentElement(element: ElementRef) {
  this._tabContentElement = element;
  console.log("scroll top: " + this.element.nativeElement);
  // anything else to execute once, when view child is found
}
Otto
  • 45
  • 1
  • 6