Understanding viewChild() Signal Queries

viewChild represents a signal-based approach for retrieving elements directly from a component's template. These queried elements can take the form of component instances or standard HTML elements.

The functionality provided by the viewChild signal query mirrors that of the traditional @ViewChild decorator.

Our exploration begins with querying basic HTML elements, followed by an examination of component queries.

Querying Plain HTML Elements with viewChild()

To query an element, we must first assign it a template reference using the # syntax.

The implementation looks like this:

@Component({
    selector: "book",
    template: `
    <div>
      <b #title>Title</b>
    </div>
  `,
})
class BookComponent {

    title = viewChild<ElementRef>("title");

    constructor() {
        effect(() => {
            console.log("Title: ", 
            this.title()?.nativeElement);
        });
    }

}

In this example, the #title template reference is attached to a native HTML element.

Following that, we utilize the viewChild signal query to locate the HTML element identified by the #title template reference.

The viewChild signal query produces a signal whose output value represents the queried element, though it is wrapped within an ElementRef.

To gain access to the query's result, we can subscribe to the title query signal using any signal-based mechanism like effect() or computed().

Examining the Signal's Return Value

It's important to note that reaching the actual native HTML element still requires accessing the nativeElement property of the signal's value. This is due to ElementRef serving as a wrapper around the DOM element rather than being the element itself.

Purpose of the ElementRef Generic Parameter

The generic parameter <ElementRef> is placed on viewChild to specify the type of values the query signal will emit. Without this specification, the signal would be considered to emit values of type unknown, which isn't ideal.

That's all there is to querying native HTML elements with the viewChild() signal query.

The AfterViewInit Lifecycle Hook

A notable difference here is the absence of the traditional AfterViewInit lifecycle hook. With the @ViewChild decorator, this hook was typically required, but with signal queries, we simply use a signal effect() to be notified when the title element becomes available.

Alternatively, we could employ computed() to derive values from the title signal.

The title signal is essentially a standard read-only signal, making it straightforward to work with and integrate with other signal-based constructs in the framework.

In principle, this means we shouldn't need AfterViewInit anymore when opting for query signals over their decorator equivalents.

Handling Multiple Occurrences of a Template Variable

<div>
  <b #title>First Title</b>
  <b #title>Second Title</b>
</div>

<p #title>Paragraph Title</b>

In scenarios where the title variable appears multiple times, viewChild will consistently select the first occurrence, without throwing any errors.

Using viewChild() for Component Queries

In addition to plain HTML elements, the viewChild feature allows us to query angular component instances. This can be achieved either by using template references as we did previously, or by specifying the component class directly.

Let's first explore querying components using template references:

@Component({
  template: `
    <div>
      <book #book></book>
    </div>
  `,
})
class BookListComponent {
  bookComponent = 
  viewChild<BookComponent>("book");
}

Here, a <book/> component appears in the template with a template reference variable named book.

When this book reference is passed to viewChild, the resulting signal will return the instance of the BookComponent itself, not the underlying HTML element.

This distinction enables direct interaction with the component instance, as demonstrated here:

this.bookComponent().title;
this.bookComponent().hello();

The generic parameter <BookComponent> is crucial here. It informs the viewChild query what type of component it should be looking for.

And that's how you can directly query component instances using viewChild.

How viewChild() Reacts to Template Changes

The viewChild signal query performs its initial lookup when the view is first initialized.

Subsequently, if the queried element is destroyed, re-rendered, or updated within the component tree, the signal query will re-execute to reflect the current state of the element.

If the element is destroyed, the signal's value will be set to undefined. However, if the element is created again, the signal query will retrieve the element from the template view once more.

In essence, the signal query refreshes its value with every change detection run.

Configuring the "read" Option on viewChild()

By default, the viewChild() query emits either:

  • an ElementRef instance when the queried element is a plain HTML element
  • a component instance when the queried element is a component

This is the standard behavior, but it can be adjusted when necessary, and there are valid reasons to do so.

For instance, even if the queried element is a component, we might still need access to its HTML element for various purposes.

Here's how that would be configured:

@Component({
  template: `
    <div>
      <book #book></book>
    </div>
  `,
})
class BookListComponent {
  bookComponent = viewChild("book", {
    read: ElementRef
  });
}

This "read" configuration instructs viewChild() to emit the ElementRef of the queried element instead of the component instance itself.

There are other scenarios where the "read" option proves valuable. For example, the queried element might have several directives applied to it, and we might want to target them specifically.

@Component({
    template: `
    <div>
      <book #book 
       matTooltip="I'm a tooltip!"> 
      </book>
    </div>
  `,
    imports: [
        MatTooltip
    ]
})
class BookListComponent {
    bookComponent = viewChild("book", {
        read: MatTooltip
    });

    constructor() {
        effect(() => {
            console.log("Tooltip: ", 
            this.bookComponent()?.message);
        });
    }
}

In this context, the value emitted by the viewChild() query will be the MatTooltip directive instance, rather than the BookComponent instance.

Therefore, when an element has multiple directives, the "read" option allows us to query them individually as needed.

Making viewChild() Queries Required

In its default state, viewChild() returns undefined if the queried element is not found in the template view.

However, we have the option to enforce that a query must always find a match in the template. If no match is found for such a required query, an error will be raised:

<div>
  <b #title>Title</b>
</div>
titleRef = viewChild.required("bold");

Since the example query above has no corresponding match, the following error occurs:

ERROR Error: NG0951: Child query result is required but no value is available.

With that, we've covered the essentials of the viewChild() signal query.

It's worth pointing out that most of these concepts apply broadly to all other signal queries, as they function in a very similar manner.

Now, let's proceed to explore the other query types.

Introducing viewChildren()

viewChildren() is the signal-based query used for retrieving multiple elements from a component's template, as opposed to a single element like viewChild().

Similar to viewChild, the elements returned can be:

  • ElementRef instances (for plain HTML elements)
  • Angular component instances
  • directives linked to a component

There are several practical applications for viewChildren(), as we'll discover.

Let's begin with the most common use case: querying a collection of components from the template.

Querying Components with viewChildren()

Here is a template featuring a list of book components:

@Component({
  template: `
        <div>
            <book/>
            <book/>
            <book/>
            <book/>
        </div>
    `,
})
class BookListComponent {

  bookComponents =
          viewChildren(BookComponent);

  constructor() {
      effect(() => {
          console.log(this.bookComponents());
      });
  }

}

The viewChildren query will locate all matching <book /> components in the template.

Executing this code will output to the console an array containing 4 BookComponent instances, ordered as they appear in the template.

But how does querying based on template references work in this context?

Using viewChildren() with Repeated Template References

One feature of viewChildren() that isn't widely known is its ability to handle the same template reference being applied to multiple elements. The function will still work as intended in this situation.

Consider this example:

@Component({
    template: `
        <div>
            <book #book>First Book</book>
            <book #book>Second Book</book>
            <book #book>Third Book</book>
        </div>
    `,
})
class BookListComponent {

    books = viewChildren<BookComponent>("book"); 

} 

Here, viewChildren() will produce an array of 3 <book /> instances.

Even when querying based on a template reference, the results will still be component instances rather than plain HTML elements. viewChildren() automatically detects that the query matches components and returns them accordingly.

In this regard, this default behavior differs slightly from that of viewChild().

Querying Plain HTML Elements with viewChildren()

Now, let's see what occurs when we apply the same template reference to several plain HTML elements instead of components:

@Component({
    template: `
        <div>
            <div #book>First Book</div>
            <div #book>Second Book</div>
            <div #book>Third Book</div>
        </div>
    `,
})
class BookListComponent {

    books = viewChildren("book"); 

} 

In this case, viewChildren() returns a signal that emits an array of ElementRef instances corresponding to the plain HTML elements.

All of this functions precisely as anticipated.

But what if we want to retrieve the HTML elements of a list of components instead of the component instances themselves?

We have a method for that as well:

@Component({
    template: `
        <div>
            <book #book>First Book</book>
            <div #book>Second Book</book>
            <div #book>Third Book</book>
        </div>
    `,
})
class BookListComponent {

    books = viewChildren<BookComponent>(
    "book", {
        read: ElementRef
    }); 

} 

By leveraging the "read" parameter, we can dictate what we want the query to return for the matched elements, rather than relying on default behavior.

We can choose to query for an ElementRef, a component, or any directive applied to the matching elements.

This completes our exploration of standard template queries using viewChild() and viewChildren().

Next, we'll turn our attention to template queries associated with content projection: contentChild() and contentChildren().

Understanding contentChild()

For the majority of template queries, viewChild() and viewChildren() are the tools you will reach for.

However, content projection introduces scenarios where these aren't sufficient.

If content projection is new to you, a thorough walkthrough can be found here: Angular Content Projection: Complete Guide.

For those who need a refresher, let's summarize the core concept.

Angular's content projection capability allows you to build highly adaptable and reusable components.

Consider a scenario where we want to add a custom feature element to the BookComponent by passing it as content, like this example:

@Component({
  selector: "app-root",
  imports: [BookComponent],
  template: `
    <book>
      <div #feature>
      In depth guide to Angular
      </div>
    </book>
  `,
})
class AppComponent {

}

This approach is highly flexible, as the title can contain any HTML—including icons, links, or other elements—and it will still render correctly.

You have the freedom to supply any HTML, and it will be used as the title.

The component itself would be structured as follows:

@Component({
  selector: "book",
  template: `
    <div class="book">
      <div class="features-container">
        <ng-content></ng-content>
      </div>
    </div>
  `,
})
class BookComponent {

  feature = viewChild("feature");

  constructor() {
    effect(() => {
      console.log("Feature: ", 
      this.feature());
    });
  }

}

The book component simply takes all the content it receives and places it into the ng-content tag.

For a deeper dive into this mechanism, please refer to this guide.

Now, let's assume that for some reason, BookComponent needs to access the content that is being projected into it.

It actually attempts to do this by using the viewChild() signal query to get a reference to the feature element.

Will this approach be successful?...

As you might have guessed, it will not.

The reason is that the viewChild() signal query is designed only for elements that are direct children of the component's own template.

Elements projected into a component via ng-content are invisible to viewChild().

This is precisely the problem that contentChild() is designed to solve!

@Component({
  selector: "book",
  template: `
    <div class="book">
      <div class="features-container">
        <ng-content></ng-content>
      </div>
    </div>
  `,
})
class BookComponent {

  feature = contentChild("feature");

  constructor() {
    effect(() => {
      console.log("Feature: ", 
      this.feature());
    });
  }

}

With this change, the implementation works as expected.

An interesting point is that we didn't need to employ the AfterContentInit lifecycle hook to access the projected elements, unlike when using the @ContentChild decorator-based approach.

Instead, we simply utilized a signal effect() to be notified when the projected element became available.

Differentiating viewChild and contentChild

With these points in mind, the distinction between viewChild() and contentChild() is clear:

  • viewChild() is for querying elements that are direct children within the component's own private template.
  • contentChild() is for querying elements that are projected into the component via ng-content. These elements originate from the parent component's template, not the component making the query.

In all other respects, they function identically.

You have the ability to use contentChild() to query components, raw HTML elements, or directives among any content that has been projected via ng-content.

To further illustrate, let's explore some additional examples of using contentChild().

Querying Components with contentChild()

Let's say the parent component is projecting a <feature /> component into the BookComponent:

@Component({
  selector: "app-root",
  imports: [BookComponent, FeatureComponent],
  template: `
    <book>
      <feature>
      In depth guide to Angular
      </feature>
    </book>
  `,
})
class AppComponent {

}

You have the option to query components within the projected content in this manner:

@Component({
  selector: "book",
  template: `
    <div class="book">
      <div class="features-container">
        <ng-content></ng-content>
      </div>
    </div>
  `,
})
class BookComponent {

  feature = contentChild(FeatureComponent);

  constructor() {
    effect(() => {
      console.log("Feature: ", 
      this.feature());
    });
  }

}

As shown, this adheres to the same convention as viewChild(), including all its rules, features, and defaults.

Leveraging "read" with contentChild()

The "read" option, which we saw with viewChild(), is also available for contentChild() and behaves in the same manner:

@Component({
  selector: "book",
  template: `
    <div class="book">
      <div class="features-container">
        <ng-content></ng-content>
      </div>
    </div>
  `,
})
class BookComponent {

  feature = contentChild("feature", {
    read: ElementRef
  });

  constructor() {
    effect(() => {
      console.log("Feature: ", 
      this.feature());
    });
  }

}

This option will return the ElementRef of the matched element rather than the component instance, provided the matching element is a component.

Setting contentChild() as required

Just as with viewChild(), you can also designate a contentChild() as required:

  feature = contentChild.required("feature");

Notice that the API and its usage for contentChild() are fully uniform and consistent with viewChild().

Understanding one means you automatically understand the other.

exploring contentChildren()

The contentChildren() function serves the same purpose for content projection as viewChildren() does for view queries.

It enables the querying of multiple elements that have been projected into a component through ng-content, in contrast to just a single one.

As an illustration, let's look at this parent configuration that projects several elements into the BookComponent:

@Component({
  selector: "app-root",
  imports: [BookComponent],
  template: `
    <book>
      <div #title>Title 1</div>
      <div #title>Title 2</div>
      <div #title>Title 3</div>
    </book>
  `,
})
class AppComponent {

}

Here, we see plain HTML title elements being projected into the BookComponent.

To query these elements from within BookComponent, we would use the contentChildren() function:

bookTitles = contentChildren("title");

This returns a signal that emits an array of ElementRef instances for all queried elements.

But what happens when we project components instead of plain HTML elements?

Here's an example:

@Component({
  selector: "app-root",
  imports: [BookComponent, TitleComponent],
  template: `
    <book>
      <title>Title 1</title>
      <title>Title 2</title>
      <title>Title 3</title>
    </book>
  `,
})
class AppComponent {

}

To retrieve the projected <title /> components, you'd write:

bookTitles = contentChildren(TitleComponent);

Naturally, you could also use the "read" option to query directives applied to these elements.

This is how you'd locate the ElementRef of the projected <title /> components:

bookTitles = contentChildren("title", {
  read: ElementRef
});

This method works with any other directive applied to the projected elements.

With this, we've now covered all the signal queries available in Angular.

Let's now provide a concise recap of everything we've learned and conclude.

Summary

In this comprehensive discussion, we've looked at every signal-based query available: viewChild(), viewChildren(), contentChild(), and contentChildren().

We clearly saw their benefits over the older decorator-based methods:

  • they offer a more intuitive and simpler API
  • they integrate smoothly with other signals
  • they often eliminate the need for lifecycle hooks

These queries represent a considerable improvement to the framework. Combined with other signal-based APIs, they make developing with Angular an enjoyable experience.

The default behavior and configuration options are perfectly aligned across all these queries. So, familiarity with the view query variant means you can be instantly productive with its content query counterpart.

If you haven't yet explored the other signal-based APIs like input(), output(), and model(), we highly suggest you familiarize yourself with them:

Angular Signal Components: input, output, model (Complete Guide)

We encourage you to try these new signal-based component authoring APIs in your projects. Feel free to ask any questions in the comments below.

We're glad to help!