Two fancy ways to handle events in Angular
This post is inspired by the following articles by Netanel Basal:
One of my favorite libraries is Vue. There’s a cool feature in this library that I want to steal for my Angular…netbasal.com
Getting to know the EventManagerPlugin in Angularnetbasal.com
Suppose that you want to apply a common behavior within an event handler, for example:
- prevent default behavior,
- stop event propagation,
- fire handler once only,
- throttle events,
- debounce events.
Although you can repeat the same code within each event handler, there are two ways to encapsulate the desired behavior and reuse in a clever way.
In this post I’ll focus on a quite common issue, namely stopping an event propagation.
The first solution relies on a specific directives’ syntax, hence I’ll start with a short reminder.
First, a directive just like a component may emit an event, therefore it’s perfectly fine to have the clicked EventEmitter. Next, you can apply an alias to a class property with the Output decorator in the same way as for the Input one. Last but not least, since the clicked EventEmitter is aliased outside of the ClickedDirective class, you can simply bind to the event and apply the directive with a single expression:
Having brushed up the knowledge of Angular directives, we can take a look at the first solution.
In order to stop event propagation, you can bind to an event emitted by a directive instead of binding directly to a native event. The directive will be responsible for listening to the native event. Next it will perform all the necessary operations like calling the stopPropagation method on an Event instance. Finally, the directive will push the event object as a next notification to the event emitter. As a result, a client will receive an Event instance as if there was a binding to a native event. However, the event won’t propagate thanks to the behavior encapsulated within the directive.
The key points include:
- using an alias for the EventEmitter equal to the directive’s selector,
- releasing resources within the ngOnDestroy method.
As a result, in the following example the like method will be invoked only once.
Custom Event Manager Plugin
The second solution relies on the so-called event manager plugin. In a nutshell, you can determine how Angular will handle a given event based on an event’s name. There are several predefined plugins, however you can provide custom ones with the aid of Dependency Injection.
The token ships with Angular so the only thing you need to do is create a dedicated service and provide it under the token. Note that you need to use multi: true, since you don’t want to override the existing entities under the token, but you want to append the object to an array of existing plugins.
The service has to contain the two methods, namely:
- supports(eventName: string): boolean
which determines if a plugin should be used for a given event,
- addEventListener(el: HTMLElement, eventName: string, originalHandler: Function): Function
which is responsible for adding an enhanced event handler (preventing event propagation) and returning a dispose function.
Let’s take a look at the implementation:
Since, you want to stop event propagation for events having the following pattern: eventName.stop, you simply check if an event’s name ends with stop keyword.
Second, you need to extract a native event’s name with the aid of the split method.
Next, you define an enhanced callback which, in addition to invoking the original handler, stop event propagation and perform a console.log (a placeholder for awesome stuff 😄).
Finally, you add an event listener to a target element with the enhanced handler and return a callback allowing to remove the event listener when the element is removed from the DOM.
Although you can invoke the stopPropagation method on an Event instance within each event handler manually, it’s always a good idea to take advantage of the features that Angular provides and encapsulate the common behavior within either an attribute directive or a custom event manager plugin.