Angular 2 @ViewChild annotation returns undefined

TypescriptAngular

Typescript Problem Overview


I am trying to learn Angular 2.

I would like to access to a child component from a parent component using the @ViewChild Annotation.

Here some lines of code:

In BodyContent.ts I have:

import { ViewChild, Component, Injectable } from 'angular2/core';
import { FilterTiles } from '../Components/FilterTiles/FilterTiles';

@Component({
    selector: 'ico-body-content',
    templateUrl: 'App/Pages/Filters/BodyContent/BodyContent.html',
    directives: [FilterTiles] 
})
export class BodyContent {
    @ViewChild(FilterTiles) ft: FilterTiles;

    public onClickSidebar(clickedElement: string) {
        console.log(this.ft);
        var startingFilter = {
            title: 'cognomi',
            values: [ 'griffin', 'simpson' ]
        }
        this.ft.tiles.push(startingFilter);
    } 
}

while in FilterTiles.ts:

 import { Component } from 'angular2/core';

 @Component({
     selector: 'ico-filter-tiles',
     templateUrl: 'App/Pages/Filters/Components/FilterTiles/FilterTiles.html'
 })
 export class FilterTiles {
     public tiles = [];

     public constructor(){};
 }

Finally here the templates (as suggested in comments):

BodyContent.html

<div (click)="onClickSidebar()" class="row" style="height:200px; background-color:red;">
    <ico-filter-tiles></ico-filter-tiles>
</div>

FilterTiles.html

<h1>Tiles loaded</h1>
<div *ngFor="#tile of tiles" class="col-md-4">
  	 ... stuff ...
</div>

FilterTiles.html template is correctly loaded into ico-filter-tiles tag (indeed I am able to see the header).

Note: the BodyContent class is injected inside another template (Body) using DynamicComponetLoader: dcl.loadAsRoot(BodyContent, '#ico-bodyContent', injector):

import { ViewChild, Component, DynamicComponentLoader, Injector } from 'angular2/core';
import { Body } from '../../Layout/Dashboard/Body/Body';
import { BodyContent } from './BodyContent/BodyContent';

@Component({
    selector: 'filters',
    templateUrl: 'App/Pages/Filters/Filters.html',
    directives: [Body, Sidebar, Navbar]
})
export class Filters {

    constructor(dcl: DynamicComponentLoader, injector: Injector) {
       dcl.loadAsRoot(BodyContent, '#ico-bodyContent', injector);
       dcl.loadAsRoot(SidebarContent, '#ico-sidebarContent', injector);
   } 
}

The problem is that when I try to write ft into the console log, I get undefined, and of course I get an exception when I try to push something inside the "tiles" array: 'no property tiles for "undefined"'.

One more thing: FilterTiles component seems to be correctly loaded, since I'm able to see the html template for it.

Any suggestions?

Typescript Solutions


Solution 1 - Typescript

I had a similar issue and thought I'd post in case someone else made the same mistake. First, one thing to consider is AfterViewInit; you need to wait for the view to be initialized before you can access your @ViewChild. However, my @ViewChild was still returning null. The problem was my *ngIf. The *ngIf directive was killing my controls component so I couldn't reference it.

import { Component, ViewChild, OnInit, AfterViewInit } from 'angular2/core';
import { ControlsComponent } from './controls/controls.component';
import { SlideshowComponent } from './slideshow/slideshow.component';

@Component({
  selector: 'app',
  template: `
    <controls *ngIf="controlsOn"></controls>
    <slideshow (mousemove)="onMouseMove()"></slideshow>
  `,
  directives: [SlideshowComponent, ControlsComponent],
})
export class AppComponent {
  @ViewChild(ControlsComponent) controls: ControlsComponent;

  controlsOn: boolean = false;

  ngOnInit() {
    console.log('on init', this.controls);
    // this returns undefined
  }

  ngAfterViewInit() {
    console.log('on after view init', this.controls);
    // this returns null
  }

  onMouseMove(event) {
    this.controls.show();
    // throws an error because controls is null
  }
}

Hope that helps.

EDIT
As mentioned by @Ashg below, a solution is to use @ViewChildren instead of @ViewChild.

Solution 2 - Typescript

The issue as previously mentioned is the ngIf which is causing the view to be undefined. The answer is to use ViewChildren instead of ViewChild. I had similar issue where I didn't want a grid to be shown until all the reference data had been loaded.

html:

   <section class="well" *ngIf="LookupData != null">
       <h4 class="ra-well-title">Results</h4>
       <kendo-grid #searchGrid> </kendo-grid>
   </section>

Component Code

import { Component, ViewChildren, OnInit, AfterViewInit, QueryList  } from '@angular/core';
import { GridComponent } from '@progress/kendo-angular-grid';

export class SearchComponent implements OnInit, AfterViewInit
{
	//other code emitted for clarity

	@ViewChildren("searchGrid")
    public Grids: QueryList<GridComponent>

	private SearchGrid: GridComponent

	public ngAfterViewInit(): void
    {
        
       	this.Grids.changes.subscribe((comps: QueryList <GridComponent>) =>
       	{
       		this.SearchGrid = comps.first;
       	});

       
    }
}

Here we are using ViewChildren on which you can listen for changes. In this case any children with the reference #searchGrid. Hope this helps.

Solution 3 - Typescript

You could use a setter for @ViewChild()

@ViewChild(FilterTiles) set ft(tiles: FilterTiles) {
    console.log(tiles);
};
    

If you have an ngIf wrapper, the setter will be called with undefined, and then again with a reference once ngIf allows it to render.

My issue was something else though. I had not included the module containing my "FilterTiles" in my app.modules. The template didn't throw an error but the reference was always undefined.

Solution 4 - Typescript

What solved my problem was to make sure static was set to false.

@ViewChild(ClrForm, {static: false}) clrForm;

With static turned off, the @ViewChild reference gets updated by Angular when the *ngIf directive changes.

Solution 5 - Typescript

This worked for me.

My component named 'my-component', for example, was displayed using *ngIf="showMe" like so:

<my-component [showMe]="showMe" *ngIf="showMe"></my-component>

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.

You can use the QueryList that @ViewChildren returns and subscribe to any changes to the referenced items using rxjs as seen below. @ViewChild does not have this ability.

import { Component, ViewChildren, ElementRef, OnChanges, QueryList, Input } from '@angular/core';
import 'rxjs/Rx';

@Component({
    selector: 'my-component',
    templateUrl: './my-component.component.html',
    styleUrls: ['./my-component.component.css']
})
export class MyComponent implements OnChanges {

  @ViewChildren('ref') ref: QueryList<any>; // this reference is just pointing to a template reference variable in the component html file (i.e. <div #ref></div> )
  @Input() showMe; // this is passed into my component from the parent as a    

  ngOnChanges () { // ngOnChanges is a component LifeCycle Hook that should run the following code when there is a change to the components view (like when the child elements appear in the DOM for example)
    if(showMe) // this if statement checks to see if the component has appeared becuase ngOnChanges may fire for other reasons
      this.ref.changes.subscribe( // subscribe to any changes to the ref which should change from undefined to an actual value once showMe is switched to true (which triggers *ngIf to show the component)
        (result) => {
          // console.log(result.first['_results'][0].nativeElement);                                         
          console.log(result.first.nativeElement);                                          

          // Do Stuff with referenced element here...   
        } 
      ); // end subscribe
    } // end if
  } // end onChanges 
} // end Class

Hope this helps somebody save some time and frustration.

Solution 6 - Typescript

My solution to this was to replace *ngIf with [hidden]. Downside was all the child components were present in the code DOM. But worked for my requirements.

Solution 7 - Typescript

My workaround was to use [style.display]="getControlsOnStyleDisplay()" instead of *ngIf="controlsOn". The block is there but it is not displayed.

@Component({
selector: 'app',
template:  `
    <controls [style.display]="getControlsOnStyleDisplay()"></controls>
...

export class AppComponent {
  @ViewChild(ControlsComponent) controls:ControlsComponent;

  controlsOn:boolean = false;

  getControlsOnStyleDisplay() {
    if(this.controlsOn) {
      return "block";
    } else {
      return "none";
    }
  }
....

Solution 8 - Typescript

In my case, I had an input variable setter using the ViewChild, and the ViewChild was inside of an *ngIf directive, so the setter was trying to access it before the *ngIf rendered (it would work fine without the *ngIf, but would not work if it was always set to true with *ngIf="true").

To solve, I used Rxjs to make sure any reference to the ViewChild waited until the view was initiated. First, create a Subject that completes when after view init.

export class MyComponent implements AfterViewInit {
  private _viewInitWaiter$ = new Subject();

  ngAfterViewInit(): void {
    this._viewInitWaiter$.complete();
  }
}

Then, create a function that takes and executes a lambda after the subject completes.

private _executeAfterViewInit(func: () => any): any {
  this._viewInitWaiter$.subscribe(null, null, () => {
    return func();
  })
}

Finally, make sure references to the ViewChild use this function.

@Input()
set myInput(val: any) {
    this._executeAfterViewInit(() => {
        const viewChildProperty = this.viewChild.someProperty;
        ...
    });
}

@ViewChild('viewChildRefName', {read: MyViewChildComponent}) viewChild: MyViewChildComponent;

Solution 9 - Typescript

It must work.

But as Günter Zöchbauer said there must be some other problem in template. I have created kinda Relevant-Plunkr-Answer. Pleas do check browser's console.

boot.ts

@Component({
selector: 'my-app'
, template: `<div> <h1> BodyContent </h1></div>

      <filter></filter>
      
      <button (click)="onClickSidebar()">Click Me</button>
  `
, directives: [FilterTiles] 
})


export class BodyContent {
    @ViewChild(FilterTiles) ft:FilterTiles;

    public onClickSidebar() {
        console.log(this.ft);
        
        this.ft.tiles.push("entered");
    } 
}

filterTiles.ts

@Component({
     selector: 'filter',
    template: '<div> <h4>Filter tiles </h4></div>'
 })


 export class FilterTiles {
     public tiles = [];

     public constructor(){};
 }

It works like a charm. Please double check your tags and references.

Thanks...

Solution 10 - Typescript

For me using ngAfterViewInit instead of ngOnInit fixed the issue :

export class AppComponent implements OnInit {
  @ViewChild('video') video;
  ngOnInit(){
    // <-- in here video is undefined
  }
  public ngAfterViewInit()
  {
    console.log(this.video.nativeElement) // <-- you can access it here
  }
}

Solution 11 - Typescript

My solution to this was to move the ngIf from outside of the child component to inside of the child component on a div that wrapped the whole section of html. That way it was still getting hidden when it needed to be, but was able to load the component and I could reference it in the parent.

Solution 12 - Typescript

This works for me, see the example below.

import {Component, ViewChild, ElementRef} from 'angular2/core';

@Component({
    selector: 'app',
    template:  `
        <a (click)="toggle($event)">Toggle</a>
        <div *ngIf="visible">
          <input #control name="value" [(ngModel)]="value" type="text" />
        </div>
    `,
})

export class AppComponent {

    private elementRef: ElementRef;
    @ViewChild('control') set controlElRef(elementRef: ElementRef) {
      this.elementRef = elementRef;
    }

    visible:boolean;

    toggle($event: Event) {
      this.visible = !this.visible;
      if(this.visible) {
        setTimeout(() => { this.elementRef.nativeElement.focus(); });
      }
    }

}

Solution 13 - Typescript

I had a similar issue, where the ViewChild was inside of a switch clause that wasn't loading the viewChild element before it was being referenced. I solved it in a semi-hacky way but wrapping the ViewChild reference in a setTimeout that executed immediately (i.e. 0ms)

Solution 14 - Typescript

A kind of generic approach:

You can create a method that will wait until ViewChild will be ready

function waitWhileViewChildIsReady(parent: any, viewChildName: string, refreshRateSec: number = 50, maxWaitTime: number = 3000): Observable<any> {
  return interval(refreshRateSec)
    .pipe(
      takeWhile(() => !isDefined(parent[viewChildName])),
      filter(x => x === undefined),
      takeUntil(timer(maxWaitTime)),
      endWith(parent[viewChildName]),
      flatMap(v => {
        if (!parent[viewChildName]) throw new Error(`ViewChild "${viewChildName}" is never ready`);
        return of(!parent[viewChildName]);
      })
    );
}


function isDefined<T>(value: T | undefined | null): value is T {
  return <T>value !== undefined && <T>value !== null;
}

Usage:

  // Now you can do it in any place of your code
  waitWhileViewChildIsReady(this, 'yourViewChildName').subscribe(() =>{
      // your logic here
  })

Solution 15 - Typescript

If an *ngIf="show" prevents a ViewChild from being rendered and you need the ViewChild right after your show turns true, it helped me to fire ChangeDetectorRef.detectChanges() immediately after I set show true.

After that the *ngIf creates the component and renders the ViewChild, s.t. you can use it afterwards. Just typed a quick sample code.

@ViewChild(MatSort) sort: MatSort;    

constructor(private cdRef: ChangeDetectorRef) {}

ngOnInit() {
  this.show = false;
  this.someObservable()
    .pipe(
      tap(() => {
        this.show = true;
        this.cdRef.detectChanges();
      })
    )
    .subscribe({
      next: (data) => {
        console.log(sort)
        this.useResult(data);
      }
    });
}

Is this bad, or why has no one proposed it?

Solution 16 - Typescript

Use [hidden] instead of *ngif because *ngif kills your code when the condition is not satisfied.

<div [hidden]="YourVariable">
   Show Something
</div>

Solution 17 - Typescript

I fix it just adding SetTimeout after set visible the component

My HTML:

<input #txtBus *ngIf[show]>

My Component JS

@Component({
  selector: "app-topbar",
  templateUrl: "./topbar.component.html",
  styleUrls: ["./topbar.component.scss"]
})
export class TopbarComponent implements OnInit {
 
  public show:boolean=false;

  @ViewChild("txtBus") private inputBusRef: ElementRef;

  constructor() {
   
  }

  ngOnInit() {}

  ngOnDestroy(): void {

  }

 
  showInput() {
    this.show = true;
    setTimeout(()=>{
      this.inputBusRef.nativeElement.focus();
    },500);
  }
}

Solution 18 - Typescript

In my case, I knew the child component would always be present, but wanted to alter the state prior to the child initializing to save work.

I choose to test for the child until it appeared and make changes immediately, which saved me a change cycle on the child component.

export class GroupResultsReportComponent implements OnInit {

    @ViewChild(ChildComponent) childComp: ChildComponent;

    ngOnInit(): void {
        this.WhenReady(() => this.childComp, () => { this.childComp.showBar = true; });
    }

    /**
     * Executes the work, once the test returns truthy
     * @param test a function that will return truthy once the work function is able to execute 
     * @param work a function that will execute after the test function returns truthy
     */
    private WhenReady(test: Function, work: Function) {
        if (test()) work();
        else setTimeout(this.WhenReady.bind(window, test, work));
    }
}

Alertnatively, you could add a max number of attempts or add a few ms delay to the setTimeout. setTimeout effectively throws the function to the bottom of the list of pending operations.

Solution 19 - Typescript

For me the problem was I was referencing the ID on the element.

@ViewChild('survey-form') slides:IonSlides;

<div id="survey-form"></div>

Instead of like this:

@ViewChild('surveyForm') slides:IonSlides;

<div #surveyForm></div>

Solution 20 - Typescript

If you're using Ionic you'll need to use the ionViewDidEnter() lifecycle hook. Ionic runs some additional stuff (mainly animation-related) which typically causes unexpected errors like this, hence the need for something that runs after ngOnInit, ngAfterContentInit, and so on.

Solution 21 - Typescript

For Angular: Change *ngIf with display style 'block' or 'none' in HTML.

selector: 'app',
template:  `
    <controls [style.display]="controlsOn ? 'block' : 'none'"></controls>
    <slideshow (mousemove)="onMouseMove()"></slideshow>
`,
directives: [SlideshowComponent, ControlsComponent]

Solution 22 - Typescript

just adding {static: true} to @View Solves my problem.

@ViewChild(FilterTiles, { static : true }) ft: FilterTiles;

Solution 23 - Typescript

Here's something that worked for me.

@ViewChild('mapSearch', { read: ElementRef }) mapInput: ElementRef;

ngAfterViewInit() {
  interval(1000).pipe(
        switchMap(() => of(this.mapInput)),
        filter(response => response instanceof ElementRef),
        take(1))
        .subscribe((input: ElementRef) => {
          //do stuff
        });
}

So I basically set a check every second until the *ngIf becomes true and then I do my stuff related to the ElementRef.

Solution 24 - Typescript

I had a similar issue in which a ViewChild was inside a conditionally (*ngIf) rendered component. Which would become rendered on the response of an api call. The response came later than when the @ViewChild decorator was executed and so the desired component reference stayed undefined (null). After using {static: false} the @ViewChild decorator wasn't fired again even when the desired component was visible after some (small) amount of time. This was against the 'promise' of Angular  (as stated in other answers in this thread)

The reason for this was ChangeDetectionStrategy was set to OnPush . When changing this to ChangeDetectionStrategy.Default all worked as expected.

Conclusion:

  1. ✅ Use { static: false } &
  2. ChangeDetectionStrategy.Default

for @ViewChild components that are conditionally (*ngIf) rendered to get their reference "later on" (when they become rendered)

Solution 25 - Typescript

I resolved this issue with the help of change detection along with delayed initialization of view container reference.

HTML setup:

<ng-container *ngIf="renderMode === 'modal'" [ngTemplateOutlet]="renderModal">
</ng-container>
<ng-container *ngIf="renderMode === 'alert'" [ngTemplateOutlet]="renderAlert">
</ng-container>

<ng-template #renderModal>
  <div class="modal">
    <ng-container appSelector></ng-container>
  </div>
</ng-template>

<ng-template #renderAlert>
  <div class="alert">
    <ng-container appSelector></ng-container>
  </div>
</ng-template>

Component:

@ViewChild(SelectorDirective, { static: true }) containerSelector!: SelectorDirective;

constructor(private cdr: ChangeDetectorRef) { }

ngOnInit(): void {
  // step: 1
  this.renderMode = someService.someMethod();
  // step: 2
  this.cdr.markForCheck();
  // step: 3
  const viewContainerRef = this.containerSelector?.viewContainerRef;
  if (viewContainerRef) {
    // logic...
  }
}
  1. Modified the code such that condition on which HTML is dependent on (*ngIf), should update first
  2. Once the condition is updated, manually trigger ChangeDetection
  3. Get the reference from ViewChild after manual cdr trigger and proceed ahead with logic.

Solution 26 - Typescript

Apart from the other answers you can also use the last life-cycle hook:

ngAfterViewChecked() {}

ngAfterViewChecked is called even after ngAfterViewInit

Life-cycle hooks: https://angular.io/guide/lifecycle-hooks#lifecycle-event-sequence

Solution 27 - Typescript

The solution which worked for me was to add the directive in declarations in app.module.ts

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAndrea IalentiView Question on Stackoverflow
Solution 1 - TypescriptkenecaswellView Answer on Stackoverflow
Solution 2 - TypescriptAshgView Answer on Stackoverflow
Solution 3 - TypescriptparliamentView Answer on Stackoverflow
Solution 4 - TypescriptPaul LeBeauView Answer on Stackoverflow
Solution 5 - TypescriptJoshua DyckView Answer on Stackoverflow
Solution 6 - Typescriptsuzanne suzanneView Answer on Stackoverflow
Solution 7 - TypescriptCalderasView Answer on Stackoverflow
Solution 8 - TypescriptnikojpapaView Answer on Stackoverflow
Solution 9 - TypescriptmicronyksView Answer on Stackoverflow
Solution 10 - TypescriptyayaView Answer on Stackoverflow
Solution 11 - TypescriptharmonickeyView Answer on Stackoverflow
Solution 12 - TypescriptNiZaView Answer on Stackoverflow
Solution 13 - TypescriptNikola JankovicView Answer on Stackoverflow
Solution 14 - TypescriptSergei PanfilovView Answer on Stackoverflow
Solution 15 - TypescriptdfinkiView Answer on Stackoverflow
Solution 16 - TypescriptNishchey MaithaniView Answer on Stackoverflow
Solution 17 - TypescriptOsvaldo LeivaView Answer on Stackoverflow
Solution 18 - TypescriptN-ateView Answer on Stackoverflow
Solution 19 - TypescriptJeremyView Answer on Stackoverflow
Solution 20 - TypescriptJaiView Answer on Stackoverflow
Solution 21 - TypescriptRajan KashiyaniView Answer on Stackoverflow
Solution 22 - TypescriptChapdast DevView Answer on Stackoverflow
Solution 23 - TypescriptAnjil DhamalaView Answer on Stackoverflow
Solution 24 - TypescriptBernoulli ITView Answer on Stackoverflow
Solution 25 - TypescriptAakash GoplaniView Answer on Stackoverflow
Solution 26 - TypescriptekoView Answer on Stackoverflow
Solution 27 - Typescriptnoro5View Answer on Stackoverflow