Negli ultimi mesi, Angular v17 ha introdotto una serie di nuove API per potenziare l’integrazione dei Signal nei componenti e nelle direttive. Queste includono Signal Inputs, Model Inputs e Signal Queries.
Guardando il quadro completo, ci manca solo una core API per concludere questo viaggio, e in questo articolo tratterò proprio di questa: l’ultima aggiunta di Angular v17.3.0, la nuova funzione output()
.
La nuova funzione output()
Proprio come per le altre API precedentemente citate, abbiamo ora a disposizione una nuova funzione output()
progettata per sostituire il decoratore @Output.
Per dichiarare un output, non dobbiamo far altro che richiamare la funzione output()
quando dichiariamo una proprietà di un componente o direttiva:
import { Component, output, OutputEmitterRef } from '@angular/core';
@Component({
selector: 'my-component',
standalone: true,
template: `<button (click)="emitClick($event)">Click here</button>`,
})
export class MyComponent {
buttonClick = output<MouseEvent>();
alias = output<MouseEvent>({ alias: 'aliasClick' });
emitClick(event: MouseEvent): void {
this.buttonClick.emit(event);
this.alias.emit(event);
}
}
Code language: TypeScript (typescript)
Come possiamo notare dall’esempio, questa API supporta anche la proprietà alias
ed espone la funzione emit()
.
Inoltre, possiamo metterci in ascolto dell’evento associato nei componenti padri, utilizzando la sintassi event binding di Angular nel template:
<my-component
(buttonClick)="myFunction($event)"
(aliasClick)="myFunction($event)"
/>
Code language: HTML, XML (xml)
Fin qui, tutto funziona in modo quasi identico agli output basati sul decoratore @Output.
Passiamo ora alla funzione subscribe()
, che ha subito qualche cambiamento.
Articolo consigliato: Angular Model Inputs
Come mettersi in ascolto dell’evento dinamicamente
Quando usiamo la funzione output()
otteniamo un istanza di tipo OutputEmitterRef:
buttonClick: OutputEmitterRef<MouseEvent> = output<MouseEvent>();
Code language: TypeScript (typescript)
Oltre al già citato metodo emit()
, rimasto pressoché invariato in superficie, questa classe espone anche un metodo subscribe()
per mettersi in ascolto dell’evento dinamicamente:
import { Component, Signal, effect, viewChild } from '@angular/core';
import { MyComponent } from './my-component';
@Component({
selector: 'my-parent-component',
standalone: true,
imports: [MyComponent],
template: `<my-component />`,
})
export class MyParentComponent {
myComponentRef: Signal<MyComponent> = viewChild.required(MyComponent);
constructor() {
effect(() => {
this.myComponentRef().myOutput.subscribe((event: MouseEvent) => {
console.log('Manual subscription:', event);
});
});
}
}
Code language: TypeScript (typescript)
La subscription generata da questa classe non è basata su RxJs, dunque non possiamo utilizzare la funzione pipe()
e gli operatori. Nonostante ciò, espone comunque una funzione unsubscribe()
per terminarla dinamicamente:
const subscription = this.myComponentRef().myOutput.subscribe(
(event: MouseEvent) => {
// Unsubscribes to listen only the first event, if any
subscription.unsubscribe();
}
);
Code language: TypeScript (typescript)
È inoltre completata automaticamente quando il componente, o direttiva, viene distrutto.
Le nuove funzioni rxjs-interop
Per estendere ulteriormente le potenzialità di questa nuova API, sono state inoltre introdotte due nuove funzioni nel pacchetto RxJs Interop.
outputFromObservable( )
Grazie alla funzione outputFromObservable()
possiamo ora creare un output partendo da un Observable.
L’output generato emette dunque per ogni nuovo valore emesso dell’Observable:
import { Component, OutputRef } from '@angular/core';
import { outputFromObservable } from '@angular/core/rxjs-interop';
import { interval } from 'rxjs';
@Component({
selector: 'my-timer-component',
standalone: true,
template: `...`,
})
export class MyTimerComponent {
timer: OutputRef<number> = outputFromObservable(interval(1000));
timerAlias = outputFromObservable(interval(1000), { alias: 'timerChange' });
}
Code language: TypeScript (typescript)
Sia l’Observable che l’output vengono completati automaticamente quando il componente, o direttiva, viene distrutto.
In caso di errore l’Observable viene interrotto, di conseguenza l’output smette di emettere e l’errore si propaga (se non è gestito).
outputToObservable( )
Grazie alla funzione outputToObservable()
possiamo invece trasformare un output in Observable:
import { Component, Signal, effect, viewChild } from '@angular/core';
import { outputToObservable } from '@angular/core/rxjs-interop';
import { MyComponent } from './my-component';
@Component({
selector: 'my-parent-component',
standalone: true,
imports: [MyComponent],
template: `<my-component />`,
})
export class MyParentComponent {
myComponentRef: Signal<MyComponent> = viewChild.required(MyComponent);
constructor() {
effect(() => {
outputToObservable(this.myComponentRef().myOutput)
.subscribe((event: MouseEvent) => {
console.log('Manual subscription:', event);
});
});
}
}
Code language: TypeScript (typescript)
Anche in questo caso, sia l’Observable che l’output vengono completati automaticamente quando il componente, o direttiva, viene distrutto.
Inoltre, data la mancanza di errori negli output, l’Observable risultante non emette mai notifiche di errore.
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. 👋😁