Using attribute directives to wrap legacy components

Although they have wildly different approaches to web development sometimes we have to use jQuery and Angular together. jQuery embraces and extends the DOM API while Angular is trying to abstract it away as far as it is possible. But we often find a plugin we need to use that does not have a suitable Angular port. We want to limit the “damage” and generally want to contain the dependency so that we can continue following the Angular philosophy. Turns out there’s a great tool for the job: attribute directives.

An attribute directive is similar to a component. In fact the only difference between components and directives is that components have templates. An attribute directive can have lifecycle hooks and can do pretty much everything a component can, except having its own template and styles. The attribute part of its name refers to the fact that it can be accessed and used as a HTML attributes.

Built-in attribute directives include the hide, ngStyle, formControlName and lots of others. Fortunately we can define our own directives. Instead of using the @Component decorator we have to use the @Directive decorator, but the classes are similar. We’ll write one shortly that solves the jQuery dependency problem.

There’s a jQuery plugin for emojis which is quite mature and easy to use. It’s called emojionearea. It has everything we need, but sadly uses jQuery. Wouldn’t it be great if we could reuse it in our components wherever required, but leave the DOM access logic and jQuery out of our components? We can do that with an attribute directive. Here’s the code:

Let’s see what happens here. First we import jQuery and the emojionearea package. Then we define an attribute directive using the @Directive decorator. We add a selector, where the square brackets signify that it’s an attribute. As I mentioned, directives are almost the same as components. They share the same life cycle and most importantly, the same life cycle hooks. Our directive implements the AfterViewInit interface, so we can access the rendered DOM element.

We inject ElementRef to our directive’s constructor. It is used to access the element that our directive is attached to. In the ngAfterViewInit method we use the nativeElement property of the ElementRef to access the DOM element. We create a jQuery element from it and call the emojioneArea function. And that’s all, we can invoke the jQuery plugin on any DOM element from our components.

Here’s the example usage in one of our component templates:

As you can see, it’s really just an HTML attribute. One final word of advice. As we established when we were talking about the SharedModule, anything that’s supposed to be reusable should go in there. Since directives don’t contain business logic and are used to adorn our components they should go to the SharedModule.

Source code

I’m maintaining and developing a repo on GitHub. It contains Angular7 best practices. Follow this link to the relevant part of the code.