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 🤔