Check out our Angular Book Series.

How to Access a Conditionally Created @ViewChild in Angular ngOnInit?

I've been working on an application using Dragula for drag and drop, and dom-autoscroller for scrolling while dragging.

To work, the autoscroller library needs access to the ViewChild, usually set up in ngOnInit. My problem was that we were using *ngIf to hide the scrollable container until things were added to it. As such, the ViewChild was not defined at the time that ngOnInit() ran. This post will explain the problem, and how we solved it.

The Setup

To demonstrate the problem, and our solution, I created a default project with the Angular CLI and added a single component, view1.

The view1 component accepts a single input:


@Input()
value = `default value`;

And the HTML template outputs the value:

view1 works with {{value}}

It is a simple setup. You can get the full code at the Github Repo for my Blog samples.

The Problem

In the app.component.html, add the view child:


<button (click)="child1Hidden = !child1Hidden">Toggle Sample 1 Visibility</button>
<br/>
Value Passed into Child: <input type="text" (keyup)="onSample1Change($event)" />
<br/>
<app-view1 #child1 *ngIf="!child1Hidden" ></app-view1>

The child is hidden based on the child1Hidden value, so create that in the app.component.ts:


child1Hidden = true;

I use a method to update the component's input value. Create as a ViewChild:


@ViewChild('child1') child1: View1Component;

And add the method:


onSample1Change(event) {
this.child1.value = event.currentTarget.value;
}

Note: I know this might be better implemented using HTML properties to update the value, but not all ViewChild interactions are that simple.

Inside the component class, I also want to implement OnInit interface:


export class AppComponent implements OnInit {

And add the ngOnInit() method associated with the interface:


ngOnInit() {
console.log(this.child1);
}

Because the child is hidden by default, this.child1 will be undefined at the time ngOnInit() is run:

Changing the text value before making the child visible will also return errors because our code is accessing an undefined ViewChild reference:

But, once you enable the underlying component:

We are good to go.

The question remains, how do we handle initialization code for that component? I have two solutions, one I'll cover in this post and the second in the next.

Use Hidden

We can replace the *ngIf with the hidden attribute to make this work. Modify our template code:


<button (click)="child2Hidden = !child2Hidden">Toggle Sample 2 Visibility</button>
<br/>
Value Passed into Child: <input type="text" (keyup)="onSample2Change($event)" />
<br/>
<app-view1 #child2 [hidden]="child2Hidden" ></app-view1>

This creates a parallel, similar implementation. It uses child2Hidden instead of child1Hidden. Define it in the app.component.ts:


child2Hidden = true;

Also add the OnSample2Change() method, which updates the input value of the child:


onSample2Change(event) {
this.child2.value = event.currentTarget.value;
}

Be sure to define child2 as a formal ViewChild:


@ViewChild('child2') child2: View1Component;

And output child2 inside ngOnInit():


console.log(this.child2);

Rerun the app:

There are no errors in the console even though the view is not shown.

The reason this works is that when you conditionally show or hide a component using *ngIf Angular removes, or adds it, to the DOM. So, if the value is false, the ViewChild is undefined because the component does not exist. When we use the hidden directive, the component may be hidden from view, but is still created as part of the DOM.

Final Thoughts

I have mixed feelings about using hidden to get around the problem because I am not convinced that it is a good idea to create every child as part of the DOM if it is not needed or being used at the moment. Next week I'll show you a different approach.

You can get the Get can get the full source code, and a peak at next week's sample.

Comments (Comment Moderation is enabled. Your comment will not appear until approved.)
All Content Copyright 2005, 2006, 2007, 2008, 2009 Jeffry Houser. May not be reused without permission
BlogCFC was created by Raymond Camden. This blog is running version 5.9.2.002.