Published on 4 min read
Listening to DOM Changes Using MutationObserver in Angular
Sometimes we need to react when the DOM of an element changes, for instance when its children are added or removed by code we don’t control. To explain the concept - we’ll build a simple playground with a list of Todo. We’ll implement buttons for adding a new Todo and removing an existing one.
The following image demonstrates our final result:
What is a MutationObserver?
MutationObserver is a Web API that detects node modifications due to DOM manipulations.
Unlike the removed MutationEvent, the MutationObserver listens for changes efficiently. Besides, setTimeout hacks aren’t needed anymore!
To better understand MutationObserver, let’s take a look at an example:
const node = document.querySelector('.some-element');
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => console.log(mutation));
});
observer.observe(node, {
attributes: true,
childList: true,
characterData: true,
subtree: true
});
This example detects a node through querySelector and creates a MutationObserver instance. The observer takes a function which is applied for each change (for the sake of clarity we’ll log the changes into the console). When we want to bind an observer with a specific node, we use the observe method. In addition, an options parameter is passed and indicates which types of DOM changes we want to listen for. In our example, we’re notified of every DOM change.
After registering an observer - we have to take care of the subscription. A method that cancels a subscription is called disconnect:
observer.disconnect();
You can check its browser compatibility through Can I Use.
Now, we’re ready to get into our Angular business.
Preparing a Playground
Let’s start with building a simple Angular application, which will consist of a Todo list.
First of all, we’ll create a Service that simulates an asynchronous action which takes 1000ms and produces a Todo:
@Injectable()
export class AppService {
fetchData(): Observable<string> {
return of('Todo').pipe(delay(1000));
}
}
Moreover, we need to create a Component that consists of the Todo list, and methods for manipulating the list. Therefore, we’ll attach to our Component couple of methods - addTodo and removeTodo:
@Component({
selector: 'my-app',
templateUrl: './app.component.html'
})
export class AppComponent {
public todos: string[];
constructor(private appService: AppService) {
this.todos = ['Todo', 'Todo', 'Todo'];
}
addTodo(): void {
this.appService
.fetchData()
.subscribe((todo: string) => this.todos.push(todo));
}
removeTodo(index: number): void {
this.todos.splice(index, 1);
}
}
As we know, components require an appropriate template. Let’s do it:
<ul>
<li *ngFor="let todo of todos; let i = index">
{{ todo }} <button (click)="removeTodo(i)">Remove</button>
</li>
</ul>
<button (click)="addTodo()">Add Todo</button>
So far we haven’t noticed anything unusual - pretty straightforward.
Now it’s time to build a DOM changes listener.
Building a DOM Changes Listener
What we’re going to do now is to make the Directive:
@Directive({
selector: '[domChange]'
})
export class DomChangeDirective {
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
});
}
}
We define two fields: a MutationObserver field for tracking the DOM changes and an EventEmitter field for raising custom events. If you’re not familiar with EventEmitter - you can read about it inside the template syntax docs.
As it might be seen, we use ElementRef to access the node element directly. Every DOM change in our node emits a custom event, which passes the change itself as an argument.
Our next step would be to unsubscribe the changes observer when the Directive is destroyed:
ngOnDestroy(): void {
this.changes.disconnect();
}
We also implement the OnDestroy interface in the directive:
export class DomChangeDirective implements OnDestroy {
We’re done with the Directive.
The final step would be to use it in order to track the changes of the list element:
<ul (domChange)="onDomChange($event)"></ul>
Note: onDomChange is a method which is invoked for each DOM change.
Wrapping Up
In this article, we discovered the MutationObserver Web API. Furthermore, we did a DOM changes simulation and built a Directive that listens for them.
Here’s the final application:
You’re invited to explore it through your browser’s console.
You’re welcome to share:
Join My Newsletter
Get new posts directly to your inbox.
Comments are powered by DisqusDetails
Loading comments activates Disqus, which collects information as a third-party service.
The site owner has no access to or control over the information collected by Disqus.
Related Posts

How to Sync a Design System with Claude Design
18 min read
Syncing React components into Claude Design with /design-sync: what the mirror holds, how it's verified and a step-by-step example with shadcn/ui, Tailwind v4 and Storybook.

ECMAScript - Introducing Deferred Module Evaluation with import defer
8 min read
Introducing the “import defer” proposal, a new ECMAScript import form that loads and links modules eagerly while deferring their evaluation until a namespace property is first accessed - currently at stage 3 in the TC39 process.

ECMAScript - Introducing BigInt Primitive in ES2020 (ES11)
5 min read
BigInt in ES2020 (ES11) - a new primitive for arbitrarily large integers, with its operators, comparisons and limits.