Using MutationObserver

Let’s talk now about MutationObserver . These are the things we are going to cover below:

What changes can we listen to?

Initial configuration.

Code example listening to attribute changes (GitHub link).

Code example listening to childList changes (GitHub link).

What changes can we listen to?

To initialize the MutationObserver , we need to create a configuration object that the constructor is expecting.

This object sets the type of changes we are going to listen to:

attributes : If true, we are going to listen to element attribute changes.

: If true, we are going to listen to element attribute changes. childList : If true, we are going to listen to when HTML is added or removed.

: If true, we are going to listen to when HTML is added or removed. subtree : If true, we are going to observe not only our target node but also all its descendants.

Those three are the ones we are going to see in the examples but the configuration object also works with other properties:

attributeFilter : An array of attribute’s names to listen for changes.

: An array of attribute’s names to listen for changes. characterData : To listen for text changes.

: To listen for text changes. attributeOldValue : You need to set this property to true if you want to receive the attribute’s old value in your callback function.

: You need to set this property to true if you want to receive the attribute’s old value in your callback function. characterDataOldValue : You need to set this property to true if you want to receive the text’s old value in your callback function.

Initial configuration

When you create the MutationObserver object, you need to provide as a param the callback function to be executed when a change you are listening for is fired.

We are going to see this in the code examples below but it would look something like this:

The callback param represents the function to be executed when a change is fired.

Once you have your mutationObserver object created, you need to initialize it by executing the observe function.

This function receives two params, the first is the target node (this node is going to play the role of the root node to listen for changes), and the second is the configuration object that contains the listener's config mentioned before.

For example:

In the example above, you are configuring the observer to listen for the three types of changes that we are going to work with ( attributes , childList , and subtree ). For more information about this, go to the section What changes can we listen to? of this article.

As a target, let us use the body for all our examples and, finally, let's init the observer using the observe function. This code is going to change a little bit in each of our examples but the structure will always look similar to:

We have already discussed everything that is going on in this JavaScript file.

The init function checks first for mutation observer support in the browser, and, if it is supported, it starts the mutation observer. If not, it starts the mutation events (which, as we mentioned before, is not covered in this article).

The startMutationObserver function has the following steps:

Gets the body as the target node.

Sets the config object, in this case, to listen to all changes types.

It instantiates the MutationObserver providing the callback function (not implemented yet).

providing the callback function (not implemented yet). It initializes the listeners by providing the target node and the config object to the observe function of the MutationObserver object.

The callback function receives two params, the first is a list of mutation records and the second is the observer object.

Let’s name all the mutation record properties:

target : The element where the change happened.

: The element where the change happened. type : The type of mutation that it represents (attribute, character data, or child list)

: The type of mutation that it represents (attribute, character data, or child list) oldValue : The previous value (only if oldValue properties of the mutation configuration object are set to true, we discussed this before in the What changes can we listen to? section.).

It’s important to mention that you can disconnect the interface from listening to changes at any time by using the disconnect function:

Listening to attribute changes

To listen only to changes in the attributes, we need to change the config object above to only listen to attribute changes, which, by now, you probably know how to.

The code is the same as before, with the difference being that I created a function that returns the config object to listen only to attribute changes.

It is important to mention that, with this configuration, you are only listening to changes in the attributes of the target node. If you want to listen also to changes in the attributes of child nodes you need to set the property subtree to true.

Let’s change the config to listen also for subtree attribute changes:

Now we are listening to changes in the attributes of the target node and also on all its subtrees of nodes.

Great, let's see the HTML:

This HTML is super simple and the idea is to execute a function by pressing a button which is going to change the attribute text color of the tag p . This is the function:

Again, this function is simple. It gets the element, checks the color attribute, and changes it.

Let’s make it so our callback function reacts to this attribute text color change and applies that new color to the actual text:

Our callback function, as we mentioned, receives two prams: the list of mutation records and our mutation observer.

The first thing that we are going to do is to iterate the mutations list.

Inside the for , the first thing we want to do is to generate a filtering code to avoid the execution of our logic, unless it is the actual change we want to react to.

In this case, we check, using an if statement, that the mutation attribute’s name that changed is the attribute that we care about ( data-text-color ).

If that’s the case, we take the mutation target, which is the node where the change occurs, and set the style color of that node to the color set in the data-text-color attribute.

Don’t forget to change the code of the observer initialization to use this function as the callback function:

Great! So, that’s a good example of how to work with attribute changes.

Listening to child list changes

Let’s say that we also want to add a listener for when the child list changes, this means that a child node of our target node has been added or deleted.

Again, take into consideration that, if you also want to listen to when a child node of our target node is added or removed, we need to set the subtree property to true .

In that way, we are going to be listening to when a child node is added or removed from our target node and also to when a node is added or removed from the children of our target node.

By setting the configuration object as it is shown in the image above, now you are not only listening to attribute changes but also to child list changes.

Now we can use the type property of the mutation to distinguish between which type of mutation we want to react to.

In this code example, we are reacting to attribute type changes, from our previous example, and also to the type childList changes.

For the childList type sections, we are looping through the removedNodes and the addedNodes , to show how to actually iterate through the nodes that triggered the change.

Let’s add a little bit of code:

We added some changes to our HTML to count the added nodes and to add a button to execute a function to add nodes.

In our JS, we added a function to add nodes to the DOM, this function is executed when the add node button is clicked.

We also added a function to update the node count in the HTML. This second function is going to be executed in the mutation observer callback function.

Now, in the childList section of our callback function, we are going to increase the added nodes variable every time a node (not with the name test) is added.

If we don’t put the condition of executing the code only if the node added is not of name #text when the function updateNodeCount gets executed, we would enter in an infinite loop.

This is because the function is going to replace the text, which is going to trigger a childList change, which would call the updateNodeCount again, and so on. That’s why we need that condition.

So that’s it! We went through some good examples showing how to use the MutationObserver interface.

Here is the GitHub repo, go ahead and play around with the code.