12

Is there any way to get notification when an element is removed from the DOM, either directly or as part of a subtree? It seems like the only methods available are just for directly removed nodes, but I would like to get a notification when a whole subtree that contains my node is removed.

EDIT

Seems the problem wasn't entirely clear, so I have made a challenge: https://jsbin.com/winukaf

The DOM looks like this:

<body>
  <div class="root">
    <div class="great-grand-parent">
      <div class="grand-parent">
        <div class="parent">
          <div class="leaf">
            Am I still here?
          </div>
        </div>
      </div>
    </div>
  </div>
</body>

and the challenge is to notify when any one of the elements here are removed, since that will remove the leaf node from the DOM tree.

Marius
  • 57,995
  • 32
  • 132
  • 151
  • Can you please show us your code? – Ionut Necula Oct 16 '18 at 11:49
  • @Ionut - we don't need to see it, the question's clear enough. –  Oct 16 '18 at 11:49
  • @JᴀʏMᴇᴇ, it will be clearer with the code in question. Until then we can just guess. We also have to see the effort in the question, what has he tried so far. Maybe you should read [How to create a Minimal, Complete, and Verifiable example](https://stackoverflow.com/help/mcve) – Ionut Necula Oct 16 '18 at 11:50
  • @Ionut - what do you require clarity on? No! This is why I'm correcting you - we do not need to see effort for effort's sake. This is not an academy, and too many times people ask for a show of effort so that some 'willing' has been shown in order for the answer to be deserved. This question is clear, it is deserving of an answer if anybody has one. If code is required for clarity then by all means ask for it, but I don't see that as the case here. –  Oct 16 '18 at 11:51
  • OP - specific to the question, could mutation observers help you here? https://stackoverflow.com/a/20156615/10058046 The obvious alternative is some form of polling, but I dislike that for obvious reasons. –  Oct 16 '18 at 11:54
  • 2
    Note that while the [current answer](https://stackoverflow.com/a/52834898/3702797) is pointing to the right API, it's almost always a design flaw if you need it You should keep control over your code and be able to know when it will do such a thing. – Kaiido Oct 16 '18 at 12:03

3 Answers3

14

There's a HTML5 API called MutationObserver and it has pretty good support

Here's an example:

// Element is the whatever subtree/element you need to watch over
var in_dom = document.body.contains(element);
var observer = new MutationObserver(function(mutations) {
    if (document.body.contains(element)) {
        if (!in_dom) {
            console.log("element inserted");
        }
        in_dom = true;
    } else if (in_dom) {
        in_dom = false;
        console.log("element removed");
    }

});
observer.observe(document.body, {childList: true, subtree: true});
Marius
  • 57,995
  • 32
  • 132
  • 151
Tareq El-Masri
  • 2,413
  • 15
  • 23
  • 1
    I have corrected my answer, it's not the best way, I think the most clean way is just to be aware of my code and what could delete a DOM and stick my logic there – Tareq El-Masri Oct 16 '18 at 12:05
  • 1
    Yes you got it right to the point ;-) (Ps: I saw your edit and removed my previous comment) – Kaiido Oct 16 '18 at 12:07
  • Hmm, it seems like document.body.contains is a good potential solution. I didn't realize that it would look through the entire tree. [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/API/Node/contains) – Marius Oct 16 '18 at 12:34
  • You can specify an element to watch if you want, instead of `document.body` – Tareq El-Masri Oct 16 '18 at 12:45
  • @Marius `document.body.contains` looks through entire tree but observer in this answer observes only direct children of `document.body` – barbsan Oct 16 '18 at 13:02
  • yes, the observer in this answer needs to be called with `{childList: true, subtree: true}` – Marius Oct 16 '18 at 13:07
  • `document.body.contains(element)` can be replaced with `element.isConnected` and is probably faster, but it has less browser support. – Yay295 Mar 30 '20 at 23:44
0

You should use the MutationObserver API to accomplish this. Here's MDN's example adapted to a simple scenario:

// Select the node that will be observed for mutations
var targetNode = document.getElementsByClassName('root')[0];

// Options for the observer (which mutations to observe)
var config = {
  childList: true,
  subtree: true
};

// Callback function to execute when mutations are observed
var callback = function(mutationsList, observer) {
  console.log('A child node has been added or removed.');
  console.log(mutationsList[0]);
};

// Create an observer instance linked to the callback function
var observer = new MutationObserver(callback);

// Start observing the target node for configured mutations
observer.observe(targetNode, config);

var subElement = document.getElementsByClassName('parent')[0];
var elementToRemove = document.getElementsByClassName('leaf')[0];
var anotherElement = document.getElementsByClassName('great-grand-parent')[0];
setTimeout(function() {
  subElement.removeChild(elementToRemove);
}, 500);
setTimeout(function() {
  targetNode.removeChild(anotherElement);
}, 500);

// Later, you can stop observing
// observer.disconnect();
<div class="root">
  <div class="great-grand-parent">
    <div class="grand-parent">
      <div class="parent">
        <div class="leaf">
          Am I still here?
        </div>
      </div>
    </div>
  </div>
</div>
Angelos Chalaris
  • 6,611
  • 8
  • 49
  • 75
  • This does not answer the question, since it's only able to determine if a direct child node has been removed. – Marius Oct 16 '18 at 12:12
  • @Marius please check my updated examples. Removing an element inside a direct descendant logs to the console correctly! – Angelos Chalaris Oct 16 '18 at 12:15
  • but I'm not interested in removing an element inside a direct descendant, I'm interested in removing a subtree – Marius Oct 16 '18 at 12:33
  • @Marius Like I said, if you look at the examples, removing `#a` (a direct descendant of `#app`) logs correctly. The same applies to `#c` (which is a descendant of `#a`, which in turn is a descendant of `#app`, therefore part of `#app`'s subtree). – Angelos Chalaris Oct 16 '18 at 12:35
  • 1
    @Marius it observes subtree (note option `subtree: true`), you just need to observe `document.body` – barbsan Oct 16 '18 at 12:41
  • @Marius I have updated my example to match your HTML structure. – Angelos Chalaris Oct 16 '18 at 12:46
0

Here is a ready-to-use snippet that works (I modified this answer to make it simpler, a few things were unnecessary, and I added more removal cases), using the MutationObserver HTML5 API.

var obs = new MutationObserver(mut => console.log(mut[0].removedNodes[0].id));
obs.observe(document.getElementById('root'), { childList: true, subtree: true });

setTimeout(() => { document.getElementById('leaf').remove(); }, 2000);
setTimeout(() => { document.getElementById('grand-parent').remove(); }, 4000);
setTimeout(() => { document.getElementById('great-grand-parent').outerHTML = '<div id="new-born">Hello!</div>'; }, 6000);
setTimeout(() => { document.getElementById('new-born').remove(); }, 8000);
<div id="root">
  <div id="great-grand-parent">
    <div id="grand-parent">
      <div id="parent">
        Parent
        <div id="leaf">
          Am I still here?
        </div>
      </div>
    </div>
  </div>
</div>
Basj
  • 41,386
  • 99
  • 383
  • 673