DEV Community

Michael Muscat
Michael Muscat

Posted on • Edited on

4

HostListener + Output = 🤔

A HostListener lets us listen to events.

Image description

An Output emits observable events.

Image description

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
  }
}


Enter fullscreen mode Exit fullscreen mode

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
   }
}


Enter fullscreen mode Exit fullscreen mode

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 in Renderer2 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 for Output 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!

AWS Q Developer image

Your AI Code Assistant

Automate your code reviews. Catch bugs before your coworkers. Fix security issues in your code. Built to handle large projects, Amazon Q Developer works alongside you from idea to production code.

Get started free in your IDE

Top comments (2)

Collapse
 
ezzabuzaid profile image
ezzabuzaid

I learned something new today, thank you!

I guess this is possible because Angular might be converting @Output to CustomEvent 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.

Collapse
 
santoshyadavdev profile image
Santosh Yadav

Interesting 🤔

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay