During latest months, Angular v17 has introduced a set of new function-based APIs focused on enhance Signal integration within components and directives. This includes Signal Inputs, Model Inputs and Signal Queries.
Considering the full picture, we are only missing one core API to conclude this journey, and this article is dedicated to precisely that: Angular v17.3.0 latest addition, the new output()
function.
The new output()
API
Similar to the function-based APIs introduced so far, you now have a brand new output()
function designed to replace the @Output decorator.
To declare an output, you can now use the output()
function when we declare a property of a component or directive:
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);
}
}
As you can see from the example, this API also supports the alias
property and exposes the emit()
function.
Furthermore, you can listen for the output event in the parent components, using Angular event binding syntax in the template:
<my-component
(buttonClick)="myFunction($event)"
(aliasClick)="myFunction($event)"
/>
So far, everything works almost identically to @Output decorator-based outputs.
Let’s now move on to the subscribe()
function, which brings some changes.
How to subscribe to outputs programmatically
Using the output()
function you get an instance of type OutputEmitterRef:
buttonClick: OutputEmitterRef<MouseEvent> = output<MouseEvent>();
In addition to the already mentioned emit()
method, which has remained unchanged on the surface, this class also exposes a subscribe()
method to listen to the event programmatically:
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);
});
});
}
}
The subscription generated by this class is not based on RxJs, so you can’t use the pipe()
function and operators. Nevertheless, it still exposes an unsubscribe()
function to terminate it programmatically:
const subscription = this.myComponentRef().myOutput.subscribe(
(event: MouseEvent) => {
// Unsubscribes to listen only the first event, if any
subscription.unsubscribe();
}
);
It is also automatically completed when the component, or directive, is destroyed.
New rxjs-interop functions
To further enhance the capabilities of this new API, two additional functions have been introduced in the RxJs Interop package.
outputFromObservable()
Thanks to the brand new outputFromObservable()
function, you can now create an output starting from an Observable.
The generated output emits each new value emitted by the 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' });
}
Both the Observable and the output are automatically completed when the component, or directive, is destroyed.
If an error occurs, the Observable is interrupted, consequently the output stops emitting and the error propagates (if not caught).
outputToObservable()
Thanks to the new outputToObservable()
function we can instead transform an output into an 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);
});
});
}
}
Again, both the Observable and the output are automatically completed when the component, or directive, is destroyed.
Furthermore, due to the absence of errors in the outputs, the resulting Observable never emits error notifications.
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. 👋😁
Top comments (3)
Hi Davide Passafaro,
Your tips are very useful.
Thanks for sharing.
Thank you 😁
After they said they want to make Angular more accessible and appealing, something definitely went wrong 🤦♂️😄