JRJurman
JRJurman

Reputation: 1728

Mutation Observer only triggering on Parent Element

I'm trying to use a MutationObserver to watch for removed nodes in an application library, but it appears that when a parent is removed (or replaced), it doesn't trigger the callback for all of the child elements.

<button class="RemoveListButton">Remove List</button>
<div class="Container">
  <ul class="ShoppingList">
    <li class="Item">Pasta</li>
    <li class="Item">Chips</li>
    <li class="Item">Salsa</li>
  </ul>
</div>
const removeWatcher = new MutationObserver(mutationList => {
    const removedNodes = mutationList.flatMap(m => [...m.removedNodes])
    console.log('Removing', removedNodes)
})

const container = document.querySelector('.Container')
removeWatcher.observe(container, { subtree: true, childList: true })

/* additional javascript to remove elements on click */

See JSFiddle https://jsfiddle.net/eq4ahsvc/3/

In the example, when I remove an individual list item (by clicking on it), I see in the console that the element was removed and seen by the Mutation Observer.

However, when I remove the entire list, the Mutation Observer callback is only triggered for the parent, and not the individual child elements.

Is there a way to configure this behavior? Ideally every element would trigger the MutationObserver callback, such that when removing the list, I see the trigger for removing the list, and the trigger for removing each of the child elements.

Upvotes: 0

Views: 1752

Answers (1)

CertainPerformance
CertainPerformance

Reputation: 370679

The child elements are not removed from their parent, so there's no remove event to observe. See:

const ul = document.querySelector('ul');
const li = ul.children[0];
ul.remove();

console.log(li.parentElement);
<button class="RemoveListButton">Remove List</button>
<div class="Container">
  <ul class="ShoppingList">
    <li class="Item">Pasta</li>
    <li class="Item">Chips</li>
    <li class="Item">Salsa</li>
  </ul>
</div>

As you can see, the <li>s still have a parent element of the .ShoppingList, because the .ShoppingList was removed from its container, but the individual <li>s were not removed from the .ShoppingList.

If you wanted to see events for each removed <li>, you'd have to iterate over and remove them explicitly in addition to removing the .ShoppingList:

const removeWatcher = new MutationObserver(mutationList => {
  const removedNodes = mutationList.flatMap(m => [...m.removedNodes])
  console.log('Removing', removedNodes)
})

const container = document.querySelector('.Container')
removeWatcher.observe(container, { subtree: true, childList: true })

const button = document.querySelector('.RemoveListButton')
button.onclick = () => {
  document.querySelectorAll('.ShoppingList > li').forEach(li => li.remove());
	document.querySelector('.ShoppingList').remove()
}

const listItems = document.querySelectorAll('.Item')
listItems.forEach(item => item.onclick = () => {
	event.target.remove()
})
<button class="RemoveListButton">Remove List</button>
<div class="Container">
  <ul class="ShoppingList">
    <li class="Item">Pasta</li>
    <li class="Item">Chips</li>
    <li class="Item">Salsa</li>
  </ul>
</div>

For what you want, either do that, or recursively flatten all children of every item in the removedNodes array inside the observer callback.

Upvotes: 4

Related Questions