A HostListener
lets us listen to events.
An Output
emits observable events.
Can they be used together? Yes they can! (open in Stackblitz)
@Component({
selector: "my-counter",
template: `
Counter: {{ count }}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class Counter {
@Input() count = 0
@Output()
readonly increment = interval(1000)
@HostListener("increment", ["$event"])
handleIncrement(increment) {
this.count += increment
}
}
Intended behaviour? Perhaps not!
It was a little surprising to learn that Angular components and directives can listen to custom events published by the Output
decorator. This gives us a convenient way to subscribe to an observable without cleaning up afterwards; the subscription is automatically disposed when the component is destroyed. We do however pollute the output namespace in the process πββοΈ
No need for share
either since outputs are automatically multicast to parent template subscribers. It even works with OnPush
. What else can we do?
Example With HTTP Request
Let's fetch some todos.
const handleError = ctx => source => source.pipe(
catchError((error, source) =>
ctx.handleError(error) ? source : EMPTY
)
)
@Component({
template: `
I have {{ todos.length }} todos left to do
`
})
export class Counter {
protected todos = []
@Output()
readonly response = inject(HttpClient)
.get("https://jsonplaceholder.typicode.com/todos")
.pipe(handleError(this))
@HostListener("response", ["$event"])
handleResponse(todos) {
this.todos = todos
}
handleError(error: unknown) {
console.error(error)
return true
}
}
Since Angular outputs are readonly new values cannot be assigned like AsyncPipe
. Instead we need to handle errors to prevent the subscription from collapsing.
Other Things You Can't Do
- The
listen
event inRenderer2
sadly doesn't share this behaviour. - Subscribing to the same event twice with
HostListener
is not possible, only the last subscription will be used. - This won't work in
AppComponent
since there's no parent view forOutput
to subscribe to.
Summary
This is just a neat little trick saves you some subscription management without any third party libs. I hope you find it useful.
Happy coding!
Top comments (2)
I learned something new today, thank you!
I guess this is possible because Angular might be converting
@Output
toCustomEvent
and attaching it to the component element.The
HostListener
would just use normal 'addEventListener' on the component element. That method can listen to both custom and built-in events and therefore works albeit this can be considered a bug as well!This is obviously an unknown way to listen to custom events and might not be a good idea to use it because it will create confusion.
Interesting π€