Wrap your library in an Angular directive
Tags: angular, directive, javascript, typescript
Published: 2022-06-29 09:36:05

---
tags: angular, directive, javascript, typescript
series: A JS library for every framework taste
--- 

This is the second article in the series on designing a flexible JS library to be used in multiple frameworks. 

In the first article in the series, we have built a vanilla TS/JS library for swipe detection in the browser. Although it can be used in your application built with any popular JS framework as is, we want to go a little further and make our library a first-class citizen when used in the framework of your choice.

In this article, we are going to wrap our swipe detection library in an Angular directive. 

:bulb: The article implies you are familiar with the public interface of the swipe detection library used under the hood. If you haven't read the first article in the series, [this section](https://dev.to/this-is-learning/detect-swipes-with-reactive-programming-1bph#defining-the-public-interface-of-the-library) alone will be enough to follow along with the material of this one.

### How should it work

When we need to detect swipes on an element in our Angular component, doing this should be as easy as attaching a dedicated attribute directive to the target element: 

```html
  <div ngSwipe (swipeEnd)="onSwipeEnd($event)">Swipe me!</div>
```

An [attribute directive](https://angular.io/guide/attribute-directives) will be just enough here as we are not going to manipulate the DOM.

### Getting access to the host element

Let's recall what our swipe subscription expects. According to the public interface of the underlying library, we should provide the following configuration:

```typescript
export function createSwipeSubscription({
    domElement,
    onSwipeMove,
    onSwipeEnd
  }: SwipeSubscriptionConfig): Subscription {
// ...
}
```

So we need to get access to the host element our directive is attached to and pass one to the `createSwipeSubscription` function. This is a no-brainer type of task for our Angular component:

```typescript
constructor(
  private elementRef: ElementRef
) {}
```

The `nativeElement` property of the injected `elementRef` holds the reference to the underlying native DOM element. So when creating a swipe subscription, we can use this reference to pass the target DOM element: 

```typescript
this.swipeSubscription = createSwipeSubscription({
  domElement: this.elementRef.nativeElement,
  //..
});
```

### Complete solution

The rest of the directive code is pretty straightforward. Here is the complete solution:

```typescript
import { Directive, ElementRef, EventEmitter, NgZone, OnDestroy, OnInit, Output } from '@angular/core';
import { Subscription } from 'rxjs';
import { createSwipeSubscription, SwipeEvent } from 'ag-swipe-core';

@Directive({
  selector: '[ngSwipe]'
})
export class SwipeDirective implements OnInit, OnDestroy {
  private swipeSubscription: Subscription | undefined;

  @Output() swipeMove: EventEmitter<SwipeEvent> = new EventEmitter<SwipeEvent>();
  @Output() swipeEnd: EventEmitter<SwipeEvent> = new EventEmitter<SwipeEvent>();

  constructor(
    private elementRef: ElementRef,
    private zone: NgZone
  ) {}

  ngOnInit() {
    this.zone.runOutsideAngular(() => {
      this.swipeSubscription = createSwipeSubscription({
        domElement: this.elementRef.nativeElement,
        onSwipeMove: (swipeMoveEvent: SwipeEvent) => this.swipeMove.emit(swipeMoveEvent),
        onSwipeEnd: (swipeEndEvent: SwipeEvent) => this.swipeEnd.emit(swipeEndEvent)
      });
    });
  }

  ngOnDestroy() {
    this.swipeSubscription?.unsubscribe?.();
  }
}
```

The directive does the following simple routine:

- Gets the reference of the underlying DOM element.
- Creates a swipe subscription with `onSwipeMove` and `onSwipeEnd` event handlers that emit to directive's `Output`s 
whenever a relevant event occurs.
- Unsubscribes when the `ngOnDestroy` hook is called (host component is being destroyed).


We also need to ship our directive in an Angular module that the consuming application will be importing:

```typescript
@NgModule({
  imports: [CommonModule],
  declarations: [SwipeDirective],
  exports: [SwipeDirective]
})
export class SwipeModule {}
```

By the way, this is [no longer the only option](https://angular.io/guide/standalone-components). We are just not hipster enough to use a cutting edge feature like standalone directives in a public library yet.
 
### A couple of things worth mentioning
###### zone.runOutsideAngular()
You may have noticed we have one more provider injected: 
```typescript
private zone: NgZone
```

And later used to wrap the swipe subscription in `zone.runOutsideAngular`. This is a common practice of avoiding unnecessary change detection triggers on every tracked asynchronous event happening in the DOM. In our case, we don't want the change detection to be triggered excessively on every `mousemove` event. 


###### Subscribing to both swipeMove and swipeEnd
The public interface of the `ag-swipe-core` library we used under the hood allows providing only one of two event handlers: `onSwipeMove` and `onSwipeEnd`. In the Angular wrapper though, we avoid additional input parameters and always handle both events and leave it up to the directive consumer to only listen to the `Output` it is interested in. 

In this case, it is a conscious choice to prefer a simpler directive contract to possible performance optimization. I believe that simplicity should prevail over the optional optimization when it makes sense, but it is a subject for discussion of course.


### Wrapping up

You can find the complete library code on GitHub by [this link](https://github.com/aGoncharuks/ag-swipe/blob/master/libs/swipe-angular/src/lib/swipe.directive.ts).

And the `npm` package by [this link](https://www.npmjs.com/package/ng-swipe).

That was it! We have built a simple Angular directive wrapper for our swipe detection library in 30 lines of code. Spoiler alert: the React version will be shorter. :smile: But that's a whole different story for the next article.