Reapplying an Angular Directive on DOM Changes

We needed to project HTML content loaded from the database into a component. Each time the content changes we need to add some behavior to that external content, i.e. add a class to every anchor tag. We want this behavior to be added by an Angular Directive. But a Directive will not be re-executed unless the element it is attached to is recreated or triggered in some way. We don’t want the parent component to know anything about the behavior of the Directive (that’s why you use a directive in the first place!). So, we want everything needed to make the Directive work baked directly into the Directive.

So, let’s start with the basics of the Directive, and then we’ll extend it to watch the DOM.

https://medium.com/media/6a61bd250d32cd4dae7208596b449f73/href

AfterViewInit ensures the element has content already projected through property binding in the Component template. We then find all of the anchor tags and loop through them using the Renderer2 to add our cool class to each one.

Now we just need to add the directive to the component’s HTML:

<div
[myStyleAnchors]
[innerHtml]=”{{ someSafeHtml }}”>
</div>

Voila!

But, what if someSafeHtml changes dynamically at runtime? The new HTML will get rendered, but the behavior of the Directive will not be reapplied.

Now, you could create an @Input() myStyleAnchors: string; on your Directive and bind the someSafeHtml into that. You could then use ngOnChanges to decide when to re-process the anchors. The only downside to this is that the HTML could be quite large. In our case, it can be a tremendous amount of content.

There is another way of monitoring for DOM changes, the MutationObserver. It has three methods, two of which we will cover. Note that this is not an RxJS Observable. This is native!

First, we create a new observer in our Directive that re-processes the anchors anytime the observer detects changes in the DOM:

private observer = new MutationObserver(() => this.styleAnchors());

The observe method is called with the nativeElement to be watched. Additional flags describe the specific DOM content to observe.

private registerListenerForDomChanges() {
const attributes = false;
const childList = true;
const subtree = true;
this.observer.observe(this.el.nativeElement, {
attributes,
childList,
subtree
});
}

We want to clean up when this gets destroyed, so let’s add this to ngOnDestroy:

ngOnDestroy() {
this.observer.disconnect();
}

Now, let’s hook it up!

ngAfterViewInit(){
this.styleAnchors();
this.registerListenerForDomChanges();
}

Now, putting classes on the anchor tags could cause us to infinitely loop if we passed different flags to the observe method, so let’s protect against that:

private processing = false;private styleAnchors() {
if (!this.processing) {
this.processing = true;
const anchors = this.el.nativeElement.querySelectorAll(‘a’);
anchors.forEach(a => this.renderer.addClass(a, ‘my-cool-class’));
this.processing = false;
}
}

Now, when the child DOM of this element changes contents the Directive will re-process the anchor tags!

In the Gist below I included a bonus on how to mock out everything you need for unit testing this kind of Directive as well!

Happy coding!

https://medium.com/media/dc683a3691639a93d2f424be603d9a79/href

Reapplying an Angular Directive on DOM Changes was originally published in ngconf on Medium, where people are continuing the conversation by highlighting and responding to this story.

Leave a Comment

Your email address will not be published.