SHARE
Facebook X Pinterest WhatsApp

Respond to DOM Changes in Angular with MutationObserver Web API

Written By
thumbnail
Rob Gravelle
Rob Gravelle
Jan 17, 2022

In the Respond to DOM Changes with Mutation Observers article, we learned about DOM Mutation Observers, which replaced the clunky DOM Mutation Events. Both Mutation Events and Mutation Observers were designed to inform your script of changes to the DOM structure. These include:

  • the adding or removing of elements
  • changes to an element’s attributes
  • character data changes (i.e., changes to a text node)

Looking at the above list, it quickly becomes apparent that Mutation Observers can also help Angular developers respond to DOM events that would remain elusive otherwise. In this tutorial, we will see just how easy it is to harness the power of the MutationObserver Web API to receive updates whenever a Component’s child elements change.

Introducing the Singles Catalog Management Console App

Here’s an Angular app for managing the cover art for a catalog of digital music singles:

DOM Changes in Angular Tutorial

 

It’s a very simple app that allows us to add and remove items from an unordered list. Unsurprisingly, there is not a whole lot of HTML markup required to make it work:

<h1>Singles Catalog</h1>
<ul>
  <li *ngFor="let artwork of singlesArtwork">
  <img [src]="artwork" /><button (click)="removeArtwork(artwork)">Remove</button>
  </li>
</ul>
<button *ngIf="showAddButton; else showMessage" (click)="addArtwork()">Add Next Single</button>

<ng-template #showMessage>No more singles to add.</ng-template>


Read:
Declare and Respond to a Dom Element Ready Event

Creating an AppService in Angular

The images are served up from a service via the public fetchSinglesArtwork() method. It removes elements from the start of the list and emits them as an RxJS Observable. A delay of 500 milliseconds is added in order to emulate a network call that would be employed to fetch image URLs from an external data store. It also has a method to remove images that replaces previously removed items:

import { Injectable } from '@angular/core';
import * as Rx from 'rxjs/Rx';

const ROOT = 'https://i1.sndcdn.com/artworks-';

@Injectable()
export class AppService {
  private singlesArtwork = [
    ROOT + 'nOmzKy8phBjdHggR-B4aC8Q-t200x200.jpg', 
    ROOT + 'b5jyk6LpdxWVWWz5-jGpxXw-t200x200.jpg', 
    ROOT + 'OCRwxXbSBQwex4mM-LY2qWg-t200x200.jpg',
    ROOT + '2n1JKCcFA6Gg6UH7-GsP3HA-t200x200.jpg',
    ROOT + 'NXjocVbpYjLOCwSx-9VELtA-t200x200.jpg'
  ];

  public fetchSinglesArtwork(howMany: number = 1): Rx.Observable<string[]> {
    return Rx.Observable
      .of(this.singlesArtwork.splice(0, howMany))
      .delay(500);
  }

  public removeSinglesArtwork(artwork: string): void {
    this.singlesArtwork.push(artwork);
  }
}

Fetching the Initial List

On startup, our app shows the first three images. To fetch them, the AppComponent injects our service into the constructor and invokes its fetchSinglesArtwork() method. In the subscription callback function, the URLs to the singles artwork are stored in the public singlesArtwork variable that is referenced from the template. It is a Set rather than a regular array so that we can guarantee that the list never contains duplicates. Of course, if the service is coded properly, that should never happen.

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  private singlesArtwork: Set<string>;
  public showAddButton = true;

  constructor(private appService: AppService) { 
    appService.fetchSinglesArtwork(3)
      .subscribe(singlesArtwork => this.singlesArtwork = new Set<string>(singlesArtwork));
  }
  // more code...
}

Read: RXJS Observables Primer in Angular

Adding an Item to the List

Clicking the Add Next Single button invokes the addArtwork() method, which in turn calls fetchSinglesArtwork(). In the addArtwork() subscription callback function, the artwork is added as long as there are elements left in the array; otherwise, the Add button is replaced by a message stating that there are no more singles to add:

public addArtwork(): void {
  this.appService
    .fetchSinglesArtwork()
    .subscribe(singlesArtwork => {
      if (singlesArtwork.length > 0) {
        this.singlesArtwork = this.singlesArtwork.add(singlesArtwork[0]);
      } else {
        this.showAddButton = false;
      }
    });
}

Removing an Item from the List

Beside each image, there is a button to remove it from the list that invokes the removeArtwork() method. It, in turn, calls the service’s removeSinglesArtwork() method, passing along the image URL so that the missing data may be replaced in the AppService’s singlesArtwork array:

public removeArtwork(artwork: string): void {
  this.singlesArtwork.delete(artwork);
  this.appService.removeSinglesArtwork(artwork);
  this.showAddButton = true;
}

The DomChange Directive in Angular

In Angular applications, the MutationObserver API is probably best implemented as a directive. The ElementRef is injected as a constructor parameter so that we can access the node element directly. Then every DOM change emits a custom event that passes the change itself as an argument:

import { Directive, ElementRef, EventEmitter, OnDestroy, Output } from '@angular/core';

@Directive({
  selector: '[domChange]'
})
export class DomChangeDirective implements OnDestroy {
  private changes: MutationObserver;

  @Output()
  public domChange = new EventEmitter();

  constructor(private elementRef: ElementRef) {
    const element = this.elementRef.nativeElement;

    this.changes = new MutationObserver((mutations: MutationRecord[]) => {
        mutations.forEach((mutation: MutationRecord) => this.domChange.emit(mutation));
      }
    );

    this.changes.observe(element, {
      attributes: true,
      childList: true,
      characterData: true
    });
  }

  ngOnDestroy(): void {
    this.changes.disconnect();
  }
}

We can now bind the directive to the onDomChange() handler in the template as follows:

<ul (domChange)="onDomChange($event)">

In this case, the $event will be an ElementRef to the <ul> element.

In the AppComponent’s onDomChange() handler, we can respond to the DOM changes any way we wish. For demonstration purposes, we’ll just look for the addition and removal of <li> elements and output the first one to the console:

public onDomChange(mutationRecord: MutationRecord): void {
  const addedNodes   = mutationRecord.addedNodes;
  const removedNodes = mutationRecord.removedNodes;

  if (  addedNodes.length > 0
    && (addedNodes.item(0) as HTMLElement).tagName === 'LI') {
    console.log('Added nodes: ', addedNodes.item(0));
  } else if (removedNodes.length > 0
         && (removedNodes.item(0) as HTMLElement).tagName === 'LI') {
    console.log('Removed nodes: ', removedNodes.item(0));
  }
}

Note that both the addedNodes and removedNodes elements are Node objects in TypeScript, so we have to cast them as an HTMLElement in order to gain access to its attributes and methods. We can see some sample output here:

Dom and Angular Tutorials

 

You’ll find the Singles Catalog application on stackblitz.

Conclusion

Thanks to the MutationObserver Web API, we can do things in our Angular apps that would be very difficult to otherwise. Just be careful to respect the component hierarchy and avoid listening to changes within other components. For instance, if you need to respond to DOM changes within a child component, add the directive to its template and then pass up the data you need to the parent via an EventEmitter.

Read: Graceful RxJS Error Handling

Recommended for you...

The Revolutionary ES6 Rest and Spread Operators
Rob Gravelle
Aug 23, 2022
Ahead of Time (AOT) Compilation in Angular
Tariq Siddiqui
Aug 16, 2022
Converting a JavaScript Object to a String
Rob Gravelle
Aug 14, 2022
Understanding Primitive Type Coercion in JavaScript
Rob Gravelle
Jul 28, 2022
HTML Goodies Logo

The original home of HTML tutorials. HTMLGoodies is a website dedicated to publishing tutorials that cover every aspect of being a web developer. We cover programming and web development tutorials on languages and technologies such as HTML, JavaScript, and CSS. In addition, our articles cover web frameworks like Angular and React.JS, as well as popular Content Management Systems (CMS) that include WordPress, Drupal, and Joomla. Website development platforms like Shopify, Squarespace, and Wix are also featured. Topics related to solid web design and Internet Marketing also find a home on HTMLGoodies, as we discuss UX/UI Design, Search Engine Optimization (SEO), and web dev best practices.

Property of TechnologyAdvice. © 2025 TechnologyAdvice. All Rights Reserved

Advertiser Disclosure: Some of the products that appear on this site are from companies from which TechnologyAdvice receives compensation. This compensation may impact how and where products appear on this site including, for example, the order in which they appear. TechnologyAdvice does not include all companies or all types of products available in the marketplace.