Tutorial hero image
Lesson icon

The Difference Between @ViewChildren and @ContentChildren

4 min read

Originally published March 24, 2017

The title of this article doesn't have quite the same air of excitement about it when compared to say Batman vs Superman, but it's a concept I struggled with for a while and I felt that it is worthy of its own blog post.

When building an application with Ionic (or more generally, with Angular) you may come across the following annotations:

  • @ViewChild
  • @ViewChildren
  • @ContentChild
  • @ContentChildren

I think the difference between child and children is reasonably obvious, the plural form would just indicate more than one. But, what is the difference between content and view? All of these perform the same general function, and that is to grab a reference to something in your template. With standard Javascript, you might do something like:

document.getElementById('some-id');

But that's not really the "Angular way" to do it. Angular is designed to be platform agnostic (i.e. it doesn't have to run on the web), so it's better to avoid working directly with the DOM wherever possible. Using @ViewChildren and ContentChildren will also return a QueryList which provides an observable that you can watch for changes, so it works much more nicely in the Angular environment than using getElementById does (as tempting as that may be).

In this tutorial, we are going to briefly cover how to grab elements using @ViewChildren and @ContentChildren, and then we will walk through what exactly the difference is between the two, and when you should use one over the other. I provide a little summary of the difference between @ViewChild and @ContentChild at the end of this post, but it might also be useful to have that context up front:

The simplest way I can put it is that you use @ViewChild to grab elements that you have added directly to the component of that template, and you use @ContentChild to grab elements that have been added to the components template through content projection.

Outline

How do they work?

Although they look somewhat intimidating, using these annotations to grab elements is quite straight forward. You use them to set up member variables above your constructor that you can use throughout your component.

You can supply these with either a local variable or you can provide it with the class of the component you are attempting to grab. So, you could do something like this:

@ViewChildren(Item) items;

so that you could access all of the <ion-item> elements in your template through this.items, or you could do:

@ViewChild('someVariable') something;

to grab a reference to an element that you have set up a local template variable on, like this:

<some-component #someVariable></some-component>

The basic idea is simple enough, but the distinction between @ViewChildren and @ContentChildren can be quite difficult to make at first.

When to use @ViewChildren

You should use @ViewChildren when you have added the element you are trying to grab directly to the component yourself, in other words, if the element you are grabbing is not added to the component you are working with through content projection with <ng-content>. If you are working with pages in an Ionic application, then this will likely be the most common scenario, and unless you are building your own custom components then you likely would only need to use @ViewChild and @ViewChildren.

I'm going to use the expandable header component from the Advanced Angular Components & Directives module as an example. Let's take a look at the template for the HomePage component:

<ion-header>
  <app-expandable-header [scrollArea]="myContent">
    <ion-item>
      <ion-label><ion-icon name="search"></ion-icon></ion-label>
      <ion-input type="text"></ion-input>
    </ion-item>

    <ion-item>
      <ion-label><ion-icon name="funnel"></ion-icon></ion-label>
      <ion-input type="text"></ion-input>
    </ion-item>
  </app-expandable-header>
  <ion-toolbar color="primary">
    <ion-title> Expandable Header </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content class="ion-padding" [scrollEvents]="true" #myContent>
  <ion-item>hello</ion-item>
  <ion-item>hello</ion-item>
</ion-content>

Everything that you see above has been added directly by me to the template, so if I want to grab any of those elements from within the context of the HomePage component, then I can do so with @ViewChild or @ViewChildren.

If I wanted to grab every <ion-item> on the page I could do so with:

@ViewChildren(Item) items;

This would return the <ion-item> elements that are inside of the <ion-content> but it will also grab the <ion-item> elements that are inside of the <app-expandable-header> tags. This is an important distinction, because as you will see in the next section, the elements that are inside of the <app-expandable-header> custom component can also be grabbed with @ContentChildren, but only from the context of the ExpandableHeader component, not the HomePage component (remember, a page in Ionic is still just a normal Angular component). I will elaborate on this in the next section, but in this case, I have added those elements directly to the HomePage template so I can grab them with @ViewChildren.

If I just wanted to grab the <ion-content> area, I could select it in a similar way to the way that I selected the Item's, but I could also use the #myContent local variable that I added by doing this:

@ViewChild('mycontent') contentArea;

or I could just do this:

@ViewChild(Content) contentArea;

since Content is the name of the class for the <ion-content> tag.

When to use @ContentChildren

You can use @ContentChildren to grab a reference to content that has been projected into a component through the use of <ng-content>. This is a subtle but important difference. If I were to try to use @ContentChildren to grab those items in the example above, by doing this:

@ContentChildren(Item) items;

It wouldn't return any results. However, let's consider the <app-expandable-header> component specifically. The template for that component looks like this:

<ng-content></ng-content>

Not much here, all it does is project whatever content is added between the <app-expandable-header> tags in the HomePage into the template for the component. I haven't added anything directly to the template here, so there is nothing to grab with @ViewChild or @ViewChildren.

However, if we look at how we were using <app-expandable-header> in the HomePage template:

<app-expandable-header [scrollArea]="myContent">
  <ion-item>
    <ion-label><ion-icon name="search"></ion-icon></ion-label>
    <ion-input type="text"></ion-input>
  </ion-item>

  <ion-item>
    <ion-label><ion-icon name="funnel"></ion-icon></ion-label>
    <ion-input type="text"></ion-input>
  </ion-item>
</app-expandable-header>

We have two <ion-item> elements that will be projected into the ExpandableHeader component's template. When we were in the context of the HomePage I could grab both of these items with @ViewChildren because I added them directly to the HomePage template. If we tried to do the same from the context of the ExpandableHeader component in expandable-header.ts then we wouldn't get any results with @ViewChildren (because these elements are not coded directly into the template for ExpandableHeader).

However, even though we haven't added these elements directly into the template for the expandable header component, they are added through content projection. This is where @ContentChildren comes in. If we instead do this inside of expandable-header.ts:

@ContentChildren(Item) items;

Then we can grab a reference to those elements that are being added through content projection.

Summary

The difference between @ViewChildren and @ContentChildren is one of those concepts that will just click in your head, and once you understand that difference it's a reasonably easy distinction to make. Getting to that point can be difficult, though, so I hope I have given a clear enough example of the difference.

The simplest way I can put it is that you use @ViewChild to grab elements that you have added directly to the component of that template, and you use @ContentChild to grab elements that have been added to the components template through content projection.

If you enjoyed this article, feel free to share it with others!