Stop Using @ViewChild/Children Decorators! Use Signals Instead

November 08, 2024 | 9 Minute Read

If you’ve been working with Angular for very long, you’re probably pretty familiar with the @ViewChild and @ViewChildren decorators. Well, if you haven’t heard yet, the framework is moving away from these decorators in favor of the new viewChild and viewChildren signal query functions. This means that it’s time for us to make the switch! In this tutorial we’ll take an existing example of both decorators and convert them each to the new respective functions.

The Starting Point: Our Current Example

Here we have this demo application that lists out several notable NBA players:

Example Angular application

This list is searchable and, when initialized, the search field is automatically focused so that the user can just start typing to filter the list:

Example of the search textbox focused on initialization using the @ViewChild decorator

We are currently using the old @ViewChild decorator to accesss the input element and programmatically set focus on initialization.

Also, each of the players visible in this list are displayed using a “player component”.

Just below the search field, we are displaying the current number of player components showing in the list, one for each player:

Example of the player components count displayed using the @ViewChildren decorator

This is probably not something you would do in the real-world, but it’s just something basic that I came up with for the purposes of this tutorial.

Then, as we filter the list, we can see this number updated to properly reflect the changes in the view:

Example of the player components count updating using the @ViewChildren decorator

This component count is being handled with the old @ViewChildren decorator and the QueryList class.

In this tutorial, we are going to convert both of these concepts to use the new signals-based approach.

Let’s start with the focusing of the search textbox.

Step 1: Converting the @ViewChild Decorator to the viewChild() Function

Let’s open the app component template to look at the code and see how this is currently implemented.

The search field input element has a “#searchField” template reference variable on it.

<input #searchField type="search" id="search" ... />

This variable is then used with the @ViewChild decorator to get a handle to the input element.

Let’s look at the TypeScript to see how.

Here’s what this decorator currently looks like:

@ViewChild('searchField') private searchField?: ElementRef<HTMLInputElement>;

This is how we are getting a handle to the input element so that we can programmatically set focus.

We then need to use the ngAfterViewInit lifecycle hook to wait until we have access to the view to set focus programmatically:

ngAfterViewInit() {
    this.searchField?.nativeElement.focus();
}

Let’s switch this concept and update it to use signals.

First, we can remove the decorator, then we’ll set this property to a signal using the new viewChild function. We’ll need to be sure to import this function from the @angular/core module.

This viewChild signal will be typed to our existing ElementRef, HTMLInputElement too.

Then, we just need to add our reference variable “searchField” name as a string as the “locator”.

import { viewChild } from "@angular/core";

private searchField = viewChild<ElementRef<HTMLInputElement>>('searchField');

Ok, at this point, our searchField property is now a signal. So, we can change how focus is set.

Instead of using the ngAfterViewInit lifecycle hook, we’ll use the new effect function which allows us to react to signal value changes.

We need to add the effect function within the constructor and we’ll need to be sure that it gets imported from the @angular/core module too.

Within this function we can move our programmatic focus and update it to use the new signal property by adding parenthesis.

import { effect } from "@angular/core";

constructor(...) {
    effect(() => {
        this.searchField()?.nativeElement.focus();
    });
}

That’s it.

Now, once we save, we’d see that the search field gets focused when initialized just like we want.

So, it will work exactly like it used to, but instead of using the old decorator, it’s now done using a more modern signals-based approach.

Step 2: Converting the @ViewChildren Decorator to the viewChildren() Function

Ok, now let’s update the player components count concept.

First, let’s switch back to the template to see how this is being rendered.

It looks like it’s just a “playerComponentsCount” property that’s being displayed with string interpolation:

<em>Player Components Visible: {{ playerComponentsCount }}</em>

Let’s switch back to the TypeScript to see how this property is being set.

The “playerComponentsCount” is just a basic number property:

protected playerComponentsCount: number = 0;

This property is updated in the “updatePlayerComponentsCount()” function.

private updatePlayerComponentsCount() {
    this.playerComponentsCount = this.playerComponents?.length ?? 0;
    this.playerComponents?.changes.pipe(takeUntilDestroyed(this.destroyRef))
        .subscribe(components => { 
            this.playerComponentsCount = components.length;
            this.changeDetectorRef.detectChanges();
        });
}

This function uses an observable subscription to the “playerComponents” QueryList “changes” observable to update the count.

So, every time the QueryList is updated, the count will be updated to the length of the list.

Ok, now let’s convert this to signals.

First, we should remove the decorator, then the QueryList.

Then, we’ll set this property equal to a signal query with the new viewChildren function.

We’ll need to be sure to import it from the @angular/core module as well.

Then, we need to pass it the PlayerComponent as its locator:

import { viewChildren } from "@angular/core";

private playerComponents = viewChildren(PlayerComponent);

Ok, this is now a signal, so we can update how the count gets set.

This is where it gets pretty cool.

Rather than use the function with the whole observable subscription, we can use a new concept where we create a signal from another signal.

This concept is known as a computed signal.

So, let’s change the “playerComponentsCount” property to use the new computed function.

We need to be sure that this also gets imported from the @angular/core module like the others.

Any signals used within this function will be used to update the value of this signal.

So, since our player components list property is now a signal, all we need to do is return its length in this computed function.

import { computed } from "@angular/core";

protected playerComponentsCount = computed(() => this.playerComponents().length);

So now, whenever the viewChildren signal is updated, its value will trigger this signal to update with its new length.

And this means that we can remove the old “updatePlayerComponentsCount” function, the ngAfterViewInit function and its imports, the takeUntilDestroyed function, the DestroyRef, and the ChangeDetectorRef too.

Lastly, we need to add parenthesis to its usage in the template:

Before:

<em>Player Components Visible: {{ playerComponentsCount }}</em>

After:

<em>Player Components Visible: {{ playerComponentsCount() }}</em>

Ok, that’s it.

When we save now it should all be working as expected.

The count should be correct right out of the gate. Then as we filter, we should still see the count update as the list changes.

So, it should all work just like it did before, but now instead of using the old decorator and QueryList, it’s done using a more modern signals-based approach.

In Conclusion

The new viewChild and viewChildren functions are a change for Angular developers for sure.

When switching to signals, it’s a different way of thinking, but these functions are easy to use, powerful, and flexible, and they make working with components and templates a breeze.

So, what are you waiting for?

Ditch those old decorators for good and update to signals with these new signal query functions instead!

Alright, I hope you found this tutorial helpful!

Don’t forget to check out my other Angular tutorials for more tips and tricks.

Additional Resources

Want to See It in Action?

Check out the demo code and examples of these techniques in the in the Stackblitz example below. If you have any questions or thoughts, don’t hesitate to leave a comment.