Question

Angular @ViewChild Returning Undefined: Lifecycle, Child Components, and Fixes

typescriptangular

Question

I am learning Angular and trying to access a child component from a parent component using @ViewChild.

In BodyContent.ts I have:

import { ViewChild, Component } 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() {
    console.log(this.ft);

    const startingFilter = {
      title: 'cognomi',
      values: ['griffin', 'simpson']
    };

    this.ft.tiles.push(startingFilter);
  }
}

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 = [];

  constructor() {}
}

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>

The FilterTiles.html template is rendered correctly inside the ico-filter-tiles tag, so the child component appears to be loading.

Also, BodyContent is inserted dynamically inside another component using DynamicComponentLoader:

import { 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 log this.ft, it is undefined, and trying to access this.ft.tiles throws an error because ft is not set.

Why can @ViewChild be undefined even when the child component is visible, and how should I access a child component correctly in Angular?

Short Answer

By the end of this page, you will understand what @ViewChild does, why it may be undefined, and when Angular actually makes a child component available. You will also learn the role of lifecycle hooks like ngAfterViewInit, how view queries relate to the component template, and safer ways to communicate between parent and child components.

Concept

@ViewChild lets a parent component get a reference to something inside its own view, usually a child component, directive, or DOM element.

In Angular, this reference is not guaranteed to exist immediately when the class is created. Angular first:

  1. Creates the parent component
  2. Builds its template
  3. Creates child components inside that template
  4. Resolves @ViewChild
  5. Runs lifecycle hooks such as ngAfterViewInit

That means @ViewChild is tied to the view lifecycle, not just the fact that some HTML is visible on screen.

A common beginner mistake is to assume:

  • if the child component is rendered,
  • then @ViewChild must already be available everywhere.

But Angular only sets the @ViewChild property after the view has been initialized.

This matters because parent components often try to use a child reference too early:

  • in the constructor
  • in ngOnInit in older Angular behavior
  • during some event that fires before the query is ready
  • when the child belongs to a different dynamically loaded component tree

In your example, the core idea is: FilterTiles is part of BodyContent's view, so BodyContent can query it with @ViewChild. But the query only becomes reliable after Angular finishes creating that view.

In real programming, this concept matters whenever you want to:

  • call a child component method from the parent
  • focus an input element
  • read a DOM element size
  • integrate with third-party UI libraries
  • trigger child setup after rendering

So the key lesson is:

@ViewChild is about querying the rendered view, and view queries become available only at the correct lifecycle stage.

Mental Model

Think of a parent component as a manager setting up a room.

  • The manager arrives first.
  • The furniture is brought in afterward.
  • Only once the furniture is placed can the manager point to a chair and say, “Use that one.”

@ViewChild is the manager pointing to a specific piece of furniture in the room.

If the manager tries to point to the chair before the room is fully set up, the result is undefined.

In Angular terms:

  • the parent component instance is created first
  • the child component instance is created later as part of the template
  • @ViewChild gets filled in only after Angular finishes assembling the view

So the mental rule is:

If you need a child from @ViewChild, wait until the view exists.

Take Quiz

Syntax and Examples

The basic syntax for querying a child component is:

import { Component, ViewChild, AfterViewInit } from '@angular/core';
import { ChildComponent } from './child.component';

@Component({
  selector: 'app-parent',
  template: `<app-child></app-child>`
})
export class ParentComponent implements AfterViewInit {
  @ViewChild(ChildComponent) child!: ChildComponent;

  ngAfterViewInit() {
    console.log(this.child);
  }
}

Example: calling a child method

import { Component, ViewChild, AfterViewInit } from '@angular/core';

@Component({
  selector: 'app-child',
  template: `<p>Child works</p>`
})
export class ChildComponent {
  message = 'Hello from child';

  addTile() {
    console.log('Tile added');
  }
}

@Component({
  selector: 'app-parent',
  template: `
    <button (click)="useChild()">Use child</button>
    <app-child></app-child>
  `
})
export class ParentComponent implements AfterViewInit {
  @ViewChild(ChildComponent) child!: ChildComponent;

  ngAfterViewInit() {
    console.log('Child is ready:', this.child.message);
  }

  useChild() {
    if (this.child) {
      this.child.addTile();
    }
  }
}

Applying the idea to your case

A safer version of your component is:

import { Component, ViewChild, AfterViewInit } 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 implements AfterViewInit {
  @ViewChild(FilterTiles) ft: FilterTiles;

  ngAfterViewInit() {
    console.log('ViewChild is ready:', this.ft);
  }

  public onClickSidebar() {
    if (!this.ft) {
      console.log('FilterTiles is not available yet');
      return;
    }

    const startingFilter = {
      title: 'cognomi',
      values: ['griffin', 'simpson']
    };

    this.ft.tiles.push(startingFilter);
  }
}

This example shows two important ideas:

  • ngAfterViewInit() is the right place to confirm the child exists.
  • Event handlers should still guard against undefined when needed.

Step by Step Execution

Consider this simplified example:

@Component({
  selector: 'app-parent',
  template: `
    <button (click)="addItem()">Add</button>
    <app-child></app-child>
  `
})
export class ParentComponent implements AfterViewInit {
  @ViewChild(ChildComponent) child: ChildComponent;

  ngAfterViewInit() {
    console.log('after view init', this.child);
  }

  addItem() {
    console.log('button clicked');
    console.log(this.child);
  }
}

Here is what happens step by step:

  1. Angular creates ParentComponent.
  2. At this moment, the child property exists as a class field, but Angular may not have assigned the real child instance yet.
  3. Angular reads the parent template.
  4. Angular sees <app-child></app-child> and creates ChildComponent.
  5. Angular resolves the @ViewChild(ChildComponent) query.
  6. Angular calls ngAfterViewInit().
  7. Inside ngAfterViewInit(), this.child is available.
  8. Later, when the button is clicked, addItem() can use this.child.

Why undefined can still happen

If you try to use this.child before step 5, it will be undefined.

That can happen if:

  • you access it in the constructor
  • you access it too early in the component lifecycle
  • the child is conditionally rendered and does not exist yet
  • the query target is not actually in the component's own view

Trace with your example

For your code, the important sequence is:

  1. BodyContent is dynamically loaded.
  2. Angular builds BodyContent.html.
  3. Angular creates FilterTiles from <ico-filter-tiles>.
  4. Angular assigns ft.
  5. You can safely use ft after the view is initialized.

So the fix is not about whether the child is visible. It is about when Angular assigns the query result.

Real World Use Cases

@ViewChild is useful when the parent needs direct access to a child component or element.

Common practical uses

  • Triggering a child method

    • Example: a parent button calls child.resetForm().
  • Reading child state

    • Example: a parent checks whether a custom form component is valid.
  • Working with DOM elements

    • Example: focusing an input after a dialog opens.
  • Using third-party widgets

    • Example: getting a chart or editor instance after it renders.
  • Layout or measurement logic

    • Example: reading element height after the page has been drawn.

Example in an app

A search page may have:

  • SearchPageComponent as parent
  • FilterPanelComponent as child
  • ResultsGridComponent as another child

The parent may use @ViewChild(FilterPanelComponent) to reset all filters when the user clicks “Clear all”.

Important note

If the goal is just to pass data from parent to child, @Input() is usually better than @ViewChild. @ViewChild is best when the parent truly needs a reference to the child instance.

Take Quiz

Real Codebase Usage

In real Angular projects, developers use @ViewChild carefully and usually combine it with clearer component communication patterns.

Common patterns

1. Parent calls a child method deliberately

@ViewChild(FilterTiles) filterTiles!: FilterTiles;

clearFilters() {
  this.filterTiles.clear();
}

This is common when the child exposes a small public API.

2. Guard clauses before use

if (!this.filterTiles) {
  return;
}

This avoids runtime errors when the child is not available yet.

3. Prefer @Input() for data flow

Instead of doing this:

this.ft.tiles.push(startingFilter);

many codebases prefer this:

<ico-filter-tiles [tiles]="tiles"></ico-filter-tiles>
export class BodyContent {
  tiles = [];

  onClickSidebar() {
    this.tiles = [
      ...this.tiles,
      { title: 'cognomi', values: ['griffin', 'simpson'] }
    ];
  }
}

And in the child:

@Input() tiles = [];

This is often easier to reason about because data flows down through inputs.

4. Use lifecycle hooks for setup

ngAfterViewInit() {
  // child is available here
}

This is the standard place for initialization that depends on a queried child.

5. Avoid tightly coupling components

When a parent directly changes a child’s internal array, the two components become more tightly connected. In larger projects, teams usually prefer:

  • @Input() for parent-to-child data
  • @Output() for child-to-parent events
  • shared services for cross-component state

That makes code easier to test and maintain.

Common Mistakes

1. Using @ViewChild too early

Broken example:

constructor() {
  console.log(this.ft); // undefined
}

Why it fails:

  • The constructor runs before Angular finishes building the view.

Fix:

  • Use ngAfterViewInit().
ngAfterViewInit() {
  console.log(this.ft);
}

2. Assuming visible HTML means the query is ready

Seeing a child component on screen does not mean the query was available at every earlier line of code.

Fix:

  • Think in lifecycle order, not just visual output.

3. Querying something that is not in the component's own view

@ViewChild only searches inside that component’s template.

Broken idea:

  • Trying to access a component that belongs to another component tree.

Fix:

  • Make sure the target really appears in the querying component’s template.

4. Mutating child internals directly when @Input() is better

Less ideal:

this.ft.tiles.push(item);

Better for many cases:

<ico-filter-tiles [tiles]="tiles"></ico-filter-tiles>

This keeps data ownership in the parent.

5. Forgetting null checks

Broken example:

this.ft.tiles.push(startingFilter);

If ft is missing, this crashes.

Fix:

if (this.ft) {
  this.ft.tiles.push(startingFilter);
}

6. Ignoring conditional rendering

If the child is inside something like *ngIf, it may not exist yet.

Example:

<ico-filter-tiles *ngIf="showTiles"></ico-filter-tiles>

If showTiles is false, @ViewChild will be undefined because the child is not in the DOM.

7. Mixing old Angular syntax with newer expectations

Your code uses Angular 2 era syntax such as:

import { Component } from 'angular2/core';

and

<div *ngFor="#tile of tiles">

That is valid for old Angular 2 examples, but modern Angular uses updated imports and microsyntax. When reading tutorials, make sure the Angular version matches the code style.

Comparisons

ConceptBest forWhen to useNotes
@ViewChildAccessing a child instance directlyCalling child methods, reading element/component referencesAvailable after view init
@Input()Passing data from parent to childParent owns data and sends it downPreferred for normal data flow
@Output()Sending events from child to parentChild needs to notify parent about actionsGood for loose coupling
Shared serviceSharing state across unrelated componentsMultiple components need common dataUseful beyond direct parent-child communication

@ViewChild vs @Input()

  • Use @ViewChild when you need a reference to the child instance.
  • Use @Input() when you need to send data to the child.

@ViewChild vs template reference variable

You can also reference a child in the template:

<ico-filter-tiles #tilesComp></ico-filter-tiles>

and then query it:

@ViewChild('tilesComp') ft: FilterTiles;

This is useful when:

  • you want to target a specific template reference
  • the child is easier to identify by variable name than by class

Direct mutation vs input-driven state

ApproachExampleProsCons
Direct child mutationthis.ft.tiles.push(item)Quick and simpleTighter coupling
Input-driven state[tiles]="tiles"Clear data flow, easier testingRequires parent to manage state
Take Quiz

Cheat Sheet

@ViewChild(ChildComponent) child: ChildComponent;

Rules

  • @ViewChild looks inside the component's own template.
  • The child reference is available after the view is initialized.
  • Use ngAfterViewInit() for setup that depends on the child.
  • If the child is conditionally rendered, the query may be undefined.
  • Prefer @Input() for normal parent-to-child data passing.

Safe pattern

import { Component, ViewChild, AfterViewInit } from '@angular/core';

export class ParentComponent implements AfterViewInit {
  @ViewChild(ChildComponent) child!: ChildComponent;

  ngAfterViewInit() {
    console.log(this.child);
  }

  doSomething() {
    if (!this.child) return;
    this.child.someMethod();
  }
}

Watch out for

  • constructor access
  • missing child in the template
  • child under *ngIf
  • version differences between old Angular 2 and modern Angular

Good default choice

  • Need to call child methods: use @ViewChild
  • Need to pass data: use @Input()
  • Need to receive events: use @Output()

FAQ

Why is @ViewChild undefined in Angular?

Because Angular has not finished creating the view yet, or because the queried child does not currently exist in the parent’s template.

When is @ViewChild available?

It is reliably available after Angular initializes the view, typically in ngAfterViewInit().

Can I use @ViewChild in the constructor?

No. The constructor runs too early. The view and its children do not exist yet.

Why is the child visible on screen but @ViewChild is still a problem?

Visibility alone does not tell you when the query became available. Angular assigns view queries as part of the component lifecycle.

Should I use @ViewChild to pass data to a child component?

Usually no. @Input() is the preferred way to pass data from parent to child.

What happens if the child is inside *ngIf?

If the condition is false, the child is not created, so @ViewChild will be undefined.

Is dynamic component loading related to this issue?

It can be. Dynamically loaded components still follow lifecycle timing. If you query a child before the dynamic view is fully initialized, the reference may not be ready.

What is the safest fix for the original code?

Use ngAfterViewInit() to confirm the child exists, add a guard before using it, and consider moving the tiles data to the parent with @Input() if the parent owns that state.

Take Quiz

Mini Project

Description

Build a small parent-child Angular example where a parent adds filter items to a child component. This project demonstrates when @ViewChild becomes available and how to safely use it without causing undefined errors.

Goal

Create a parent component that accesses a child component with @ViewChild and adds items only after the child view is ready.

Requirements

  • Create a child component with a tiles array and a method to add a tile.
  • Render the child component inside a parent component template.
  • Use @ViewChild in the parent to access the child instance.
  • Use ngAfterViewInit() to verify the child is available.
  • Add a button that safely sends a tile from the parent to the child.
Take Quiz

Keep learning

Related questions

View more