How to wrap an Angular directive library?
Tags: angular, dependencyinjection
Published: 2021-03-30 02:23:47

---
title: How to wrap an Angular directive library?
published: true
date: 2021-03-30 01:39:51 UTC
tags: Angular,DependencyInjection
canonical_url: https://blog.snowfrog.dev/how-to-wrap-an-angular-directive-library/
---

 ![How to wrap an Angular directive library?](https://blog.snowfrog.dev/content/images/2021/03/pablo--32-.png)

You've been asked to implement a new feature in the Angular application at work. As you sit at your desk and reach for the keyboard a thought pops into your mind: "I can't be the first one to have to implement something like this. I bet there's a library that does what I need".

Good for you. That's a good reflex to have in today's open-source world. Why reinvent the wheel when you can just borrow someone else's wheel? Chances are you are right; someone did have to solve the same problem you are trying to solve and was nice enough to share it with the world.

So a quick search on [npmjs.com](https://www.npmjs.com/) and you find exactly what you are looking for. The perfect Angular library which, through a few exported directives, does pretty much what you want.

Now, you realize that it might not be the best idea to start using those directives all over the app and would like to wrap that library so that your app doesn't become tightly coupled to it. But how?

When we are talking about wrapping a 3rd party library, we are usually talking about using composition to provide a new interface to our application, interface that will delegate work to the 3rd party library. That way, the 3rd party library does all the heavy lifting, but our app doesn't even know it exists, it just knows about the pretty wrapper we've made for it.

If you are familiar with design patterns, you'll probably end up using something that looks a lot like the [Adapter](https://refactoring.guru/design-patterns/adapter), the [Proxy](https://refactoring.guru/design-patterns/proxy),  or the [Façade](https://refactoring.guru/design-patterns/facade) pattern.

For our demonstration, we'll wrap the [angular-resizable-element](https://github.com/mattlewis92/angular-resizable-element) library. You can try it out, and see the code associated with this article, in the following [Stackblitz](https://stackblitz.com/edit/wrapping-angular-directive?file=src/app/app.component.html).

{% stackblitz wrapping-angular-directive %}

<h1 id="choose-your-api">Choose your API</h1>
<p><a href="https://github.com/mattlewis92/angular-resizable-element">angular-resizable-element</a> is a cool little library that makes it possible to resize elements by dragging their edges. Let's take a quick look at how it works. According to its <a href="https://mattlewis92.github.io/angular-resizable-element/docs/modules/ResizableModule.html">documentation</a>, it provides two directives through it's exported module: <code><a href="https://mattlewis92.github.io/angular-resizable-element/docs/directives/ResizableDirective.html">ResizableDirective</a></code> and <code><a href="https://mattlewis92.github.io/angular-resizable-element/docs/directives/ResizeHandleDirective.html">ResizeHandleDirective</a></code>.</p>
<p>Upon examination, we conclude that we don't really need to use <code>ResizeHandleDirective</code>. It's purpose is to give finer grain control over each handle to the sides of the resizable element and we don't really care about that. So that leaves us with <code>ResizableDirective</code>. Looking at the docs, we see that it takes in 9 inputs and emits 3 outputs.</p>
<p>As is often the case with libraries, they tend to offer a much wider API than you actually need. Do not feel like you have to mirror the 3rd party library with your wrapper. In fact, your wrapper's API should only provide what your app needs. No more, no less.</p>
<p>In our case, after a careful examination of our requirements, we determine that we don't need to provide the equivalent of the <code>allowNegativeResizes</code>, <code>mouseMoveThrottleMS</code>, <code>resizeCursors</code>, <code>resizeCursorPrecision</code> and  <code>resizeSnapGrid</code> inputs. Other than that, it would make sense for our wrapper to provide a similar interface to that of the 3rd party library, as it will cover our needs nicely.</p>
<h1 id="wrap-it-up">Wrap it up</h1>
<p>At the moment, our demo component uses the 3rd party library directly and the code looks like this:</p>
```html
<div class="text-center">
  <h1>Drag and pull the edges of the rectangle</h1>
  <div
    class="rectangle"
    [ngStyle]="style"
    mwlResizable
    [validateResize]="validate"
    [enableGhostResize]="true"
    (resizeEnd)="onResizeEnd($event)"
    [resizeEdges]="{bottom: true, right: true, top: true, left: true}"
  ></div>
</div>
```

```typescript
import { Component } from "@angular/core";
import { ResizeEvent } from "angular-resizable-element";

@Component({
  selector: "my-app",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"]
})
export class AppComponent {
  public style: object = {};

  validate(event: ResizeEvent): boolean {
    const MIN_DIMENSIONS_PX: number = 50;
    if (
      event.rectangle.width &amp;&amp;
      event.rectangle.height &amp;&amp;
      (event.rectangle.width &lt; MIN_DIMENSIONS_PX ||
        event.rectangle.height &lt; MIN_DIMENSIONS_PX)
    ) {
      return false;
    }
    return true;
  }

  onResizeEnd(event: ResizeEvent): void {
    this.style = {
      position: "fixed",
      left: `${event.rectangle.left}px`,
      top: `${event.rectangle.top}px`,
      width: `${event.rectangle.width}px`,
      height: `${event.rectangle.height}px`
    };
  }
}
```
<p>As you can see, we are using the <code>mwlResizable</code> directive selector from the library in our template and its <code>ResizeEvent</code> interface in the component. We need to use our wrapper instead. So let's do that.</p>
<h2 id="step-one-inputs-and-outputs">Step one: inputs and outputs</h2>
<p>As a first step I often find it useful to define the inputs and outputs of our wrapper. To begin we will create a new directive in a new file for our wrapper. Since we plan on providing a similar, yet simpler, interface than the one exposed by the library, we can use its <a href="https://github.com/mattlewis92/angular-resizable-element/blob/master/src/resizable.directive.ts">source code</a> as a base and simply copy the inputs and outputs that we plan to provide. After this step we end up with something like this:</p>

```typescript
@Directive({
  selector: "[resizableWrapper]"
})
export class ResizableDirective implements OnInit, OnChanges, OnDestroy {
  @Input() validateResize: (resizeEvent: ResizeEvent) =&gt; boolean;
  @Input() resizeEdges: Edges = {};
  @Input() enableGhostResize: boolean = false;
  @Input() ghostElementPositioning: "fixed" | "absolute" = "fixed";
  @Output() resizeStart = new EventEmitter&lt;ResizeEvent&gt;();
  @Output() resizing = new EventEmitter&lt;ResizeEvent&gt;();
  @Output() resizeEnd = new EventEmitter&lt;ResizeEvent&gt;();
}
```

<p>You'll also want to make sure that you don't just reuse the library's interfaces and instead provide your own. For instance, in the above code we have the <code>ResizeEvent</code> and <code>Edges</code> interfaces. We made sure to define our own, in separate files.</p>
<h2 id="step-two-constructor-parameters">Step two: constructor parameters</h2>
<p>As we will be creating an instance of the library's directive whenever we create an instance of our wrapper, we will need to pass the appropriate dependencies. Here the 3rd party directive's constructor:</p>

```typescript
constructor(
    @Inject(PLATFORM_ID) private platformId: any,
    private renderer: Renderer2,
    public elm: ElementRef,
    private zone: NgZone
  ) {
    this.pointerEventListeners = PointerEventListeners.getInstance(
      renderer,
      zone
    );
}
```

<p>So we'll need to pass in four dependencies. All four are part of the <code>@angular/core</code> package and therefore should be easy for the DI system to resolve. Let's do that now.</p>
<p>This step is not particularly hard. All we need to do is add the library's directive to our wrapper's constructor and supply Angular's DI with a factory provider.</p>

```typescript
function resizableDirectiveFactory(
  platformId: any,
  renderer: Renderer2,
  elm: ElementRef,
  zone: NgZone
) {
  return new ResizableDirective(platformId, renderer, elm, zone);
}

const resizableDirectiveProvider = { 
  provide: ResizableDirective,
  useFactory: resizableDirectiveFactory,
  deps: [PLATFORM_ID, Renderer2, ElementRef, NgZone]
};

@Directive({
  selector: "[resizableWrapper]",
  providers: [resizableDirectiveProvider]
})
export class ResizableWrapperDirective implements OnInit, OnChanges, OnDestroy {
  constructor(private library: ResizableDirective) {}
}
```

<h2 id="step-three-lifecycle-hooks">Step three: Lifecycle hooks</h2>
<p>One thing to keep in mind when wrapping a directive in Angular is that we need to account for the lifecycle hooks. They can be viewed as part of your wrapper's API. You'll probably want to have the same lifecycle hooks as the directive you are wrapping. Keeping that in mind, let's look at the three hooks we'll need to implement. <br><br>First <code>ngOnInit</code>. The first thing we wanna do is hook up the outputs.</p>

```typescript
ngOnInit(): void {
  this.library.resizeStart
    .pipe(takeUntil(this.destroy$))
    .subscribe(event =&gt; this.resizeStart.emit(event));
  this.library.resizing
    .pipe(takeUntil(this.destroy$))
    .subscribe(event =&gt; this.resizing.emit(event));
  this.library.resizeEnd
    .pipe(takeUntil(this.destroy$))
    .subscribe(event =&gt; this.resizeEnd.emit(event));
}
```

<p>Keep in mind that this example is very simple because our event interfaces are a mirror image of the library's interfaces. Were it not the case, you would have to map them to your own interfaces before emitting them.</p>
<p>Ok, all that is left is to delegate to the library's own <code>ngOnInit</code> function.</p>

```typescript
ngOnInit(): void {
  ...
  
  this.library.ngOnInit();
}
```

<p>As simple as that. Moving on to <code>ngOnChanges</code> which gets called before <code>ngOnInit</code> and every time one or more data-bound input properties change. So guess what we need to do in that function. That's right, assign our input properties... and delegate to the library's <code>ngOnChanges</code> function.</p>

```typescript
ngOnChanges(changes: SimpleChanges): void {
  if (changes.validateResize)
    this.library.validateResize = this.validateResize;
  if (changes.resizeEdges) this.library.resizeEdges = this.resizeEdges;
  if (changes.enableGhostResize)
    this.library.enableGhostResize = this.enableGhostResize;
  if (changes.ghostElementPositioning)
    this.library.ghostElementPositioning = this.ghostElementPositioning;

  this.library.ngOnChanges(changes);
}
```

<p>And finally, <code>ngOnDestroy</code></p>

```typescript
ngOnDestroy(): void {
  this.library.ngOnDestroy();
  this.destroy$.next();
}
```

<h2 id="step-four-declare-your-wrapper-and-use-it">Step four: Declare your wrapper and use it</h2>
<p>All that is left is to add our wrapper to our module and to use it in our template.</p>

```typescript
import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { FormsModule } from "@angular/forms";

import { AppComponent } from "./app.component";
import { ResizableWrapperDirective } from "../lib/resizable-wrapper.directive";

@NgModule({
  imports: [BrowserModule, FormsModule],
  declarations: [AppComponent, ResizableWrapperDirective],
  bootstrap: [AppComponent]
})
export class AppModule {}
```

<p>As you can see, our module has no references to the 3rd party <a href="https://github.com/mattlewis92/angular-resizable-element">angular-resizable-element</a> library. It only declares our wrapper directive. Our template and component also now only depend on our wrapper directive.</p>

```html
<div class="text-center">
  <h1>Drag and pull the edges of the rectangle</h1>
  <div
    class="rectangle"
    [ngStyle]="style"
    resizableWrapper
    [validateResize]="validate"
    [enableGhostResize]="true"
    (resizeEnd)="onResizeEnd($event)"
    [resizeEdges]="{bottom: true, right: true, top: true, left: true}"
  ></div>
</div>
```

<h1 id="conclusion">Conclusion</h1>
<p>Wrapping 3rd party libraries is generally good practice but it can be a challenge to do so when dealing with Angular directives. Each library is different and will require a slightly different approach but the four steps laid out in this article should serve as a good foundation.</p>