Angular Signal Queries: Simplifying DOM Querying
Tags: angular, frontend, webdev, javascript
Published: 2024-04-15 07:00:00

Following **[Signal Inputs](https://dev.to/davidepassafaro/angular-signal-inputs-road-to-signal-components-32cl)** and **[Model Inputs](https://dev.to/davidepassafaro/angular-model-inputs-two-way-binding-inputs-with-signals-2nc7)**, another Signal API has landed in the Angular ecosystem with the release of v17.2.0: **Signal Queries**.

**Signal Queries** offer an alternative approach to the decorator-based queries, namely **@ViewChild**, **@ViewChildren**, **@ContentChild** and **@ContentChildren**, supplying query results as a **Signal**.

With queries, you can retrieve references to components, directives, DOM elements, and more. Let’s see what has changed with **Signal Queries**.

---

## View queries

View queries allow you to retrieve and interact directly with elements in a component’s own template, also known as **view**.

Both **@ViewChild** and **@ViewChildren** now have a **Signal** counterpart.

![@ViewChild to viewChild(), @ViewChildren to viewChildren()](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8bjxthvyx491eilit2hw.png)

### viewChild

Using the new **`viewChild()`** function you can retrieve a single element:

```ts
import { Component, ElementRef, Signal, viewChild } from '@angular/core';

@Component({
  selector: 'my-component',
  standalone: true,
  template: '<input #inputEl />',
})
export class MyComponent {
  inputElement: Signal<ElementRef | undefined> = viewChild('inputEl');
}
```

This function accepts every parameter that **@ViewChild** supports and offers the same functionalities, providing you the query result as a **Signal**.

When a query does not find any result its value is **`undefined`**, this happens commonly using **[Control Flow](https://dev.to/davidepassafaro/angular-control-flow-the-complete-guide-1pan)** statement like **`@if`** and **`@for`**.
Because of this, a **`viewChild()`** **Signal** value type is **`ElementRef | undefined`**.

If you are sure about the presence of at least one matching result, you can use the dedicated **`viewChild.required()`** function and get rid of **`undefined`**:

```ts
import { Component, ElementRef, Signal, viewChild } from '@angular/core';

@Component({
  selector: 'my-component',
  standalone: true,
  template: '<input #inputEl />',
})
export class MyComponent {
  inputElement: Signal<ElementRef> = viewChild.required('inputEl');
}
```

But be careful, when a **required** query fails to find any results, **Angular** throws a dedicated **error**:

![Angular Error NG0951](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oqmvztiirfgc66k79g1y.png)

### viewChildren

Using the new **`viewChildren()`** function you can retrieve multiple elements:

```ts
import { Component, ElementRef, Signal, viewChildren } from '@angular/core';

@Component({
  selector: 'my-component',
  standalone: true,
  template: `
    <input #inputEl />

    @if (showSecondInput) {
      <input #inputEl />
    }
  `,
})
export class MyComponent {
  showSecondInput = true;

  inputElementList: Signal<readonly ElementRef[]> = viewChildren('inputEl');
}
```

Similarly to **`viewChild()`** API, the **`viewChildren()`** function accepts every parameter that **@ViewChildren** supports and offers the same functionalities, providing you the query result as a **Signal**.

When a **`viewChildren()`** query does not find any result, its value is an **empty array**, this guarantees the results array is always initialized.

---

## Content queries

Content queries allow you to retrieve and interact directly with elements in a component’s content.

A component’s content is represented by the elements provided thought **content projection**, nesting elements inside the component tag in the template where it is used. For example:

```html
<my-component>
  <span> Hello ;) </span>

  <input #inputEl />
</my-component>
```

> **Note:** apart for the target template where the queries are performed, **content queries** APIs and **view queries** APIs work identically.
Because of that the next section of the article totally relies on the previous one.

Similar to our previous discussion on view queries, both **@ContentChild** and **@ContentChildren** now have as well a **Signal** counterpart.

![@ContentChild to contentChild(), @ContentChildren to contentChildren()](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8x6772u18rewak6pcsct.png)

### contentChild

Using the new **`contentChild()`** and **`contentChild.required()`** functions you can retrieve a single element:

```ts
import { Component, contentChild, ElementRef, Signal } from '@angular/core';

@Component({
  selector: 'my-component',
  standalone: true,
  template: `
    <div>
      <ng-content />
    </div>
  `,
})
export class MyComponent {
  inputElement: Signal<ElementRef | undefined> = contentChild('inputEl');

  inputElementReq: Signal<ElementRef> = contentChild.required('inputEl');
}
```

Identically to the **`viewChild()`** function, when a **required `contentChild()`** function query fails to find any results, **Angular** throws a dedicated error:

![Angular Error NG0951](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oqmvztiirfgc66k79g1y.png)

### contentChildren

Using the **`contentChildren()`** function you can retrieve multiple elements:

```ts
import { Component, contentChildren, ElementRef, Signal } from '@angular/core';

@Component({
  selector: 'my-component',
  standalone: true,
  template: `
    <div>
      <ng-content />
    </div>
  `,
})
export class MyComponent {
  inputElementList: Signal<readonly ElementRef[]> = contentChildren('inputEl');
}
```

Identically to the **`viewChildren()`** function, when a **`contentChildren()`** query does not find any result, its value is an **empty array**.

---

## Some rules you need to be careful

This new functions, **`viewChild()`**, **`viewChildren()`**, **`contentChild()`** and **`contentChildren()`**, only work if used to declare queries by initializing a component or a directive property.

Calling them outside of component and directive property initialization will produce no error, but the query will not find any result:

```ts
import { Component, ElementRef, Signal, viewChild } from '@angular/core';

@Component({
  selector: 'my-component',
  standalone: true,
  template: `
    <div #el></div>
  `,
})
export class MyComponent {
  el = viewChild('el'); // It works!

  constructor() {
    const myEl: Signal<undefined> = viewChild('el'); // No error
    console.log(myEl()); // undefined
  }
}
```

---

## Signal Queries vs Decorator Queries

Having the queries results as **Signals** means that we can compose them with other **Signals**, using **`computed()`** and **`effect()`** functions.

This is a significant advantage, as it enhances the flexibility and functionality of our codebase, leading the way to all the improvements in change detection that **Signals** brought to the framework.

In addition to that, **Signal Queries** offer other benefits:

- **More predictable timing:** query results are accessible as soon as they’re available;

- **Simpler API surface:** every query result is provided as a **Signal**, and queries with multiple results (**`viewChildren()`** and **`contentChildren()`**) return always a defined array;

- **Improved type safety:** cases with **`undefined`** as possible result are fewer, thanks to **`required()`** functions and default array for multiple results;

- **More accurate type inference:** TypeScript can infer more accurate types when a type predicate is used or when you explicit a **`read`** option;

- **Lazier updates:** as for all **Signals**, Angular updates queries results lazily. This means that the read operation are performed only when your code explicitly reads the query results.

---

## Thanks for reading so far 🙏

I’d like to have your feedback so please leave a **comment**, **like** or **follow**. 👏

Then, if you really liked it, **share it** among your community, tech bros and whoever you want. And don’t forget to **[follow me on LinkedIn](https://www.linkedin.com/in/davide-passafaro/)**. 👋😁