Dopo l’introduzione dei Signal Inputs e dei Model Inputs, una nuova Signal API è disponibile nell’ecosistema Angular con la versione 17.2.0: le Signal Queries.
Le Signal Queries offrono un approccio alternativo alle query basate sui decoratori, ovvero @ViewChild
, @ViewChildren
, @ContentChild
e @ContentChildren
, fornendoci i risultati sotto forma di Signal.
Grazie alle query possiamo ricavare istanze di componenti, direttive, elementi DOM, e molto altro. Diamo uno sguardo a cosa è cambiato con le nuove Signal Queries.
⚠️ ATTENZIONE: le nuove Signal Queries sono ancora in developer preview ⚠️
View queries
Le view queries ci permettono di interagire direttamente con gli elementi presenti all’interno del template di un componente, anche detto view.
Entrambi i decoratori @ViewChild
e @ViewChildren
hanno ora una controparte Signal.
viewChild
Utilizzando la funzione viewChild
possiamo ricavare l’istanza di un singolo elemento:
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');
}
Code language: TypeScript (typescript)
Questa funzione accetta tutti i parametri accettati dal decoratore @ViewChild
, e offre le stesse funzionalità, fornendo però il risultato sotto forma di Signal.
Quando una query non trova alcun risultato, il suo valore è undefined
.
Questo caso è molto comune quando si utilizza il Control Flow con @if
e @for
.
Proprio per questo motivo, il valore di un viewChild
Signal ha come tipo ElementRef | undefined
.
Nel caso in cui la presenza di almeno un elemento che rispetta la query è certa, e vogliamo rimuovere quel undefined
, possiamo usare la funzione viewChild.required()
:
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');
}
Code language: TypeScript (typescript)
Attenzione però, quando una query required non trova alcun risultato, Angular solleva un apposito errore:
viewChildren
Utilizzando la funzione
possiamo invece ricavare l’istanza di più elementi:viewChildren
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');
}
Code language: TypeScript (typescript)
Simile a quanto visto per viewChild
, anche la funzione viewChildren
accetta tutti i parametri accettati dal decoratore @ViewChildren
, e offre le stesse funzionalità, fornendo anch’essa il risultato sotto forma di Signal.
Quando una query viewChildren
non trova alcune risultato, il suo valore è un array vuoto. Questo garantisce che qualunque risultato sia sempre accessibile in sicurezza.
Content queries
Le content queries ci permettono di interagire direttamente con gli elementi presenti all’interno del contenuto di un componente.
Per contenuto di un componente si intende il gruppo di elementi passati tramite content projection, ovvero quando inseriamo alcuni elementi all’interno del tag del nostro componente, quando lo utilizziamo in un determinato template. Ad esempio:
<my-component>
<span> Hello ;) </span>
<input #inputEl />
</my-component>
Code language: HTML, XML (xml)
Nota: a parte per la sezione di template dove vengono svolte le query, le content queries API e le view queries API sono identiche.
Per questo, la prossima sezione dell’articolo riprende quasi interamente la precedente.
Similmente a quanto abbiamo visto con le view queries, anche @ContentChild
e @ContentChildren
hanno ora una controparte Signal.
contentChild
Utilizzando le funzioni contentChild
e contentChild.required()
possiamo ricavare l’istanza di un singolo elemento:
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');
}
Code language: TypeScript (typescript)
Anche qui, come già visto per la funzione viewChild
, quando una required query non trova alcun risultato, Angular solleva un apposito errore:
contentChildren
Utilizzando la funzione
possiamo invece ricavare l’istanza di più elementi:contentChildren
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');
}
Code language: TypeScript (typescript)
Similmente a quanto abbiamo visto con la funzione viewChildren
, quando una query
non trova alcun risultato, il suo valore è un array vuoto.contentChildren
Alcune regole a cui prestare attenzione
Le nuove funzioni, viewChild
, viewChildren
, contentChild
e contentChildren
, funzionano solamente se usate per inizializzare una proprietà di un componente o direttiva.
Utilizzandole diversamente non verrà generato alcun errore, ma la query non fornirà alcun risultato:
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
}
}
Code language: TypeScript (typescript)
Signal Queries vs Decorator Queries
Ottenere i risultati delle query sotto forma di Signal significa che possiamo comporli con altri Signal
, utilizzando ad esempio le funzioni computed
ed effect
.
Questo è già di per se un grande vantaggio, poiché migliora la flessibilità e la funzionalità della nostra codebase, aprendo la strada inoltre a tutti i miglioramenti nella change detection che i Signals hanno portando all’interno framework.
In aggiunta, le Signal Queries offrono però altri vantaggi:
- Tempi più prevedibili: i risultati delle query sono accessibili non appena sono disponibili;
- Interfaccia API più semplice: ogni risultato della query viene fornito come Signal e le query con risultati multipli (
viewChildren
econtentChildren
) restituiscono sempre un array definito; - Migliore tipizzazione: i casi con possibile risultato
undefined
sono ridotti, grazie alle funzionirequired()
e all’array predefinito per le query con risultati multipli; - Inferenza più accurata: TypeScript può dedurre tipi in modo più accurati quando viene utilizzato un type predicate o quando si esplicita un’opzione
read
; - Aggiornamenti lazy: come per tutti i Signals, Angular aggiorna i risultati delle query con una strategia lazy. Ciò significa che le operazioni di lettura vengono eseguite solo quando leggiamo esplicitamente i risultati della query.
Grazie per aver letto questo articolo 🙏
Mi piacerebbe avere qualche feedback quindi grazie in anticipo per qualsiasi commento. 👋
Infine, se ti è piaciuto davvero tanto, condividilo con la tua community. 👋😁