During the latest months, Angular v17 has introduced a set of new function-based APIs focused on enhancing 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);
}
}
Code language: TypeScript (typescript)
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)"
/>
Code language: HTML, XML (xml)
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>();
Code language: TypeScript (typescript)
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);
});
});
}
}
Code language: TypeScript (typescript)
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();
}
);
Code language: TypeScript (typescript)
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' });
}
Code language: TypeScript (typescript)
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);
});
});
}
}
Code language: TypeScript (typescript)
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 know your feedback so please feel free to contact me for any. 👋
Then, if you really liked it, share it among your community, tech bros, and whoever you want. 👋😁