With the advent of Signals in Angular, we now have available a brand new way of authoring components in Angular using signal-based APIs, and without decorators.
A big part of this new way of writing components is the introduction of signal-based template queries:
- viewChild()
- contentChild()
- viewChildren()
- contentChildren()
These APIs are a new signal-based alternative to the traditional decorator-based template queries like @ViewChild
, @ContentChild
, @ViewChildren
, and
@ContentChildren
.
They work in the exact same way, except that:
- they are signal-based
- they are easier to understand and use
- they are easy to integrate with other signals
- and they usually don't require the need for lifecycle hooks
In this comprehensive guide, we'll delve into the intricacies of these signal queries, exploring their syntax, usage patterns, and advantages over traditional decorator-based queries.
Whether you're a seasoned Angular developer or a newcomer, there is something for everybody in this guide.
So without further ado, let's dive right in!
Table of Contents
- What is viewChild()?
- Querying plain HTML elements with viewChild()
- What happens if the value of a template variable occurs more than once
- viewChild() and Component Queries
- How does the viewChild() signal query work?
- Setting "read" on viewChild()
- Making viewChild() to be required
- What is viewChildren()?
- Query components with
viewChildren()
- Query-based on template references with
viewChildren()
- viewChildren() arguments
- Read different types of elements with
viewChildren()
- Differences between
viewChildren()
andviewChild()
signal queries - What is contentChild()?
- contentChild and template reference variables
- contentChild() and Component Queries
- Deriving values from contentChild() signal
- Responding to contentChild() changes
- Using "read" on contentChild()
- Making contentChild() to be required
- What is contentChildren()?
- Querying multiple elements with
contentChildren()
- Querying multiple components with
contentChildren()
- Read different types of elements with
contentChildren()
using theread
option - Traverse into projected content descendants with
contentChildren()
- Can I get undefined in my viewChild and contentChild queries?
- Can we call viewChild, contentChild outside of component and directive property initializers?
- Advantages of signal queries over decorator-based queries
- Summary
Related posts
Notice that this post covers only the new signal-based alternatives for template querying. If you are looking to learn about the previous @ViewChild, @ViewChildren, you can read all about them here:
Angular @ViewChild: In-Depth Explanation (All Features Covered)
And if you are looking to learn about the previous @ContentChild, @ContentChildren you can read all about it here:
Angular ng-content and Content Projection: Complete Guide
What is viewChild()?
viewChild
is a signal-based query used to retrieve elements from the component's template.
These elements can be either instances of other Angular components or just plain HTML elements.
The viewChild
signal query does the same thing as the @ViewChild decorator.
Let's start by learning how to query plain HTML elements, and later we will learn how to query components.
Querying plain HTML elements with viewChild()
To query an element from the template, we need to assign it a template reference, using the # syntax.
Here is how it works:
@Component({
selector: "book",
template: `
<div>
<b #title>Title</b>
</div>
`,
})
class BookComponent {
title = viewChild<ElementRef>("title");
constructor() {
effect(() => {
console.log("Title: ",
this.title()?.nativeElement);
});
}
}
As you can see, we have assigned the #title template reference to the native HTML element.
Then we used the viewChild
signal query to query the HTML element with the template reference named #title.
The viewChild
signal query returns a signal whose emitted value is the queried element itself, although wrapped in an ElementRef
.
So if we want access to the result of the query, all we have to do is to subscribe to the title
query signal using any of the signal-based APIs like effect()
or
computed()
.
What is the value returned by this signal?
Notice that to access the actual native HTML element, we still have to access the nativeElement property of the signal value.
This is because ElementRef
is a wrapper to the actual DOM element, and not the element itself.
Why did we use a generic parameter ElementRef?
Notice also that we used generic parameter <ElementRef>
on viewChild, to indicate the type of values emitted by the query signal.
If we haven't done so, the signal would be considered to emit values ot type
unknown
, which is not what we want.
And this is how you query native HTML elements using the viewChild()
signal query, it's that simple!
What about AfterViewInit?
Notice that we didn't have to use the typical AfterViewInit
lifecycle hook like we used to do when using the @ViewChild decorator.
Instead, we just used a plain signal effect() to get notified when the title element is ready.
We could also have used computed() to derive values from the title signal.
The title signal is just a plain read-only signal, so it's very easy to work with and integrates seamlessly with the other signal-based constructs of the framework.
This means that in principle we shouldn't need AfterViewInit anymore when using query signals instead of their decorator equivalent.
What happens if the value of a template variable occurs more than once?
<div>
<b #title>First Title</b>
<b #title>Second Title</b>
</div>
<p #title>Paragraph Title</b>
In this case, viewChild
will pick the first occurrence of the title
variable, and no error will be thrown.
viewChild() and Component Queries
Besides plain HTML elements, we can also query component instances using the viewChild
feature.
We can query components either by using template references like before, or by using the component class itself.
Let's start with querying components using template references:
@Component({
template: `
<div>
<book #book></book>
</div>
`,
})
class BookListComponent {
bookComponent =
viewChild<BookComponent>("book");
}
As you can see, we are using here a <book/>
component in our template, and it has a template reference variable, book
.
If we pass this book
reference to viewChild
, the query signal will return the instance of the BookComponent
, and not the corresponding HTML element.
This means that we can interact with the component instance directly, like so:
this.bookComponent().title;
this.bookComponent().hello();
Notice the use of the generic parameter <BookComponent>
. This parameter is essential to tell viewChild what type of component is expected to match this query.
And this is how you use viewChild
to directly query component instances, it's that simple!
How does viewChild() work if the template changes?
The viewChild
signal query initially performs the query when the view is initialized.
If at any point the queried element is destroyed, re-rendered, or updated from the component tree, then the signal query will perform the query again to update itself to the current state of the element.
If the element is destroyed then the value of the signal query will be undefined.
But when created again, the signal query will get the element from the template view again.
So as you can see, the signal query refreshes itself at every change detection run.
Setting "read" on viewChild()
By default, the viewChild()
query will emit as value:
- an
ElementRef
instance if the queried element is a plain HTML element - a component instance if the queried element is a component
But this is just the default behavior, we can change it if necessary!
And there are valid cases when we would like to do so.
For example, even if the queried element is a component, we might still want to access its HTML element for some reason.
So here is how we would do it:
@Component({
template: `
<div>
<book #book></book>
</div>
`,
})
class BookListComponent {
bookComponent = viewChild("book", {
read: ElementRef
});
}
This "read" configuration tells viewChild()
to emit as value the ElementRef
of the queried element, and not the component that is linked to it.
Besides this, there are other user cases where we would want to use the "read" option.
For example, the queried element might have several directives applied to it, and we might want to query them separately.
@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 case, the emitted value of the viewChild()
query will be the MatTooltip
directive instance, and not the BookComponent
instance.
So if an element has several different directives applied to it, we can use the "read" option to query them separately one by one, if necessary.
Making viewChild() to be required
By default, viewChild() will return undefined if the queried element is not found in the template view.
But we can make viewChild()
make queries that should always match something in the template view.
If there is no match for the query, then an error will be thrown:
<div>
<b #title>Title</b>
</div>
titleRef = viewChild.required("bold");
As the above query has no match, the following error will be thrown:
ERROR Error: NG0951: Child query result is required but no value is available.
And with this, we have finished our coverage of the viewChild()
signal query.
But notice that most of what we have learned here can also be applied to all other signal queries.
They all work in a very similar way.
Let's now move on to other types of queries.
What is viewChildren()?
viewChildren()
is a signal-based query used to retrieve multiple elements from the component's template, instead of just a single one like it's the case with
viewChild()
.
These elements can either be, like in the case of viewChild
:
- ElementRef instances (plain HTML elements)
- Angular component instances
- directives linked to a component
There are quite some neat use cases for viewChildren()
, as we will see!
Let's start by learning the most common case, which involves querying a list of components from the template.
Query components with viewChildren()
Here is a template with 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 find all matching <book />
components in the template.
This code will print out to the console an array of 4 BookComponent
instances, in the order that they are found in the template.
But what about querying based on template references, how does that work?
Using viewChildren() to find multiple matches of the same template reference
A little-known property of viewChildren()
is that we can apply the same template reference in multiple elements, and viewChildren()
will still work as expected.
For 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");
}
In this case, viewChildren()
will return an array of 3 <book />
instances!
Notice that just because we are querying based on a template reference, we will still get back components, and not just plain HTML elements.
viewChildren()
will notice that the query based on a template reference matches components, so it will return components automatically instead of plain HTML elements.
So in that sense, this default behavior is slightly different than the default behavior of viewChild()
.
Querying plain HTML elements with viewChildren()
Let's now see what happens if we apply instead 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()
will return a signal that emits an array of ElementRef
instances, matching the plain HTML elements.
So this all works exactly as expected.
But what if we want to query the HTML elements of a list of components, instead of the components themselves?
We can still do so in the following way:
@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 using the "read" parameter, we can specify what we want to query in the matching elements, instead of relying on defaults.
We can query by ElementRef, component, or any directive applied to the matching elements.
And with this, we have completed our coverage of regular template queries via viewChild()
and viewChildren()
.
Let's now move on to template queries related to content projection:
contentChild()
and contentChildren()
.
What is contentChild()?
Most of the template queries that you need to do will use viewChild()
or
viewChildren()
.
But there are cases related to content projection that require something else.
If you are not familiar yet with content projection, you can read about it in detail in this guide: Angular Content Projection: Complete Guide.
But in case you are not familiar with it, let me give you a quick summary here.
Content projection is a powerful feature of the Angular framework that enables building highly configurable and reusable components.
Imagine that we would like to customize the BookComponent
component by passing it a feature to be displayed in a book page, like so:
@Component({
selector: "app-root",
imports: [BookComponent],
template: `
<book>
<div #feature>
In depth guide to Angular
</div>
</book>
`,
})
class AppComponent {
}
Notice that we could potentially pass any HTML for the title, with icons, links, etc., and that would still work.
We can pass any HTML that we want and that will be used for the title.
And here is what the book component would look like:
@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 is simply taking all the content passed to it and projecting it into the ng-content
tag.
Again, for more details on how this works, have a look at this guide.
Now imagine that for some reason, the <book/>
component needs to be able to query the content projected into it.
It's actually already trying to do so using the viewChild()
signal query to access the feature element passed to it.
Will this work?...
You guessed it, it won't!
This is because the viewChild()
signal query only works for elements that are direct children of the
viewChild()
will simply not work for elements that are projected into the component via ng-content
.
But that is exactly where contentChild()
comes in!
@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());
});
}
}
This will now work as intended!
Also notice that to access the project elements, we didn't have to use the
AfterContentInit
lifecycle hook, like we used to do with the @ContentChild
decorator.
Instead, a plain signal effect() was enough to get notified when the projected element was ready.
What is the difference between viewChild and contentChild?
And so now we understand the difference between viewChild()
and
contentChild()
:
viewChild()
is used to query elements that are direct children of the component, and exist on the component's own private templatecontentChild()
is used to query elements that are projected into the component viang-content
. Those project elements exist outside the template of the component using contentChild, in their parent component's templates.
Other than that, they work exactly in the same way.
You can use contentChild()
to query components, plain HTML elements, or directives in all the content projected via ng-content
.
But just to drive this point home, here are a few more examples of how to use contentChild()
.
contentChild() and Component Queries
Imagine that the parent component is trying to project 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 can also query components in the projected content like so:
@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 you can see, this works exactly in the same way as viewChild()
, all the same rules, features, and defaults apply.
Using "read" on contentChild()
We have even a "read" option for contentChild()
, just like we had for viewChild()
, that works in the exact same way:
@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 will return as the ElementRef of the queried element, instead of the component instance, in case that the matching element is a component.
Making contentChild() to be required
And finally, we can also make contentChild()
to be required, just like we did with viewChild()
:
feature = contentChild.required("feature");
As you can see, the API and the way of using contentChild()
is 100% identical and consistent with viewChild()
.
If you know one, you know the other.
What is contentChildren()?
contentChildren()
is the content projection equivalent of viewChildren()
.
It allows us to query multiple elements projected into the component via
ng-content
, instead of just one like it's the case of viewChildren()
.
As an example, let's consider this parent component, projecting multiple 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 {
}
As we can see, we are projecting plain HTML title elements into the
BookComponent
via ng-content
.
We can query these elements from inside BookComponent
using
contentChildren()
like so:
bookTitles = contentChildren("title");
This will return a signal which emits as values a list of ElementRef instances of the queried elements.
But what if we were projecting components instead of plain HTML elements?
Here is 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 project <title />
components, we would do:
bookTitles = contentChildren(TitleComponent);
And of course, we could also query for directives applied to those elements by using the "read" option.
Here is how we would query for the ElementRef of the projected <title />
components:
bookTitles = contentChildren("title", {
read: ElementRef
});
This would work as usual with any other directive applied to the projected elements.
And with this, we have completed our full coverage of the signal queries available to us in Angular.
Let's then quickly summarize everything that we have learned, and wrap things up.
Summary
In this extensive guide, we explored in depth all the signal queries available to us: viewChild()
, viewChildren()
, contentChild()
and contentChildren()
.
We saw the advantages that they bring us over their decorator-based counterparts:
- easier to understand and use
- easy to integrate with other signals
- usually don't require the need for lifecycle hooks
These queries are a major step forward for the framework, and together with the rest of the signal-based APIs, they make Angular a breeze to work with.
The default behavior and configuration options of these queries is perfectly consistent, so if you know viewChild / viewChildren, you will automatically know contentChild / contentChildren.
And if you aren't familiar yet with the other input()
, output()
, and model()
signal-based APIs, I would recommend you to check them out as well:
Angular Signal Components: input, output, model (Complete Guide)
Go ahead and try the new signal-based component authoring APIs out in your applications, and let me know in the comments below any questions you might have.
I would be happy to help!