37

Is it possible to observer mutations on a DOM node that doesn't exist yet?

Example:

My app creates a div at some point: <div id="message" data-message-content="foo" data-message-type="bar" />.

I want to watch for the creation & change of this div.

var mutationObserver = new MutationObserver(function(mutations){
  // Some code to handle the mutation.
});

mutationObserver.observe(
    document.querySelector('#message'),
        { 
            attributes: true, 
            subtree: true, 
            childList: true, 
            characterData: false 
        }
    );
);

Right now this returns an error since #message is null (the div hasn't been created yet).

Failed to execute 'observe' on 'MutationObserver': parameter 1 is not of type 'Node'.

An obvious solution is to watch the body and check if any of the mutations are the creation of div#Message, but this seems like a bad idea / or possibly bad for performance.

Don P
  • 60,113
  • 114
  • 300
  • 432
  • How are you creating the element? Can't you add this in the same method that creates it so it is actually created then? – StudioTime Aug 10 '16 at 18:51
  • Unfortunately no - this is for a Google Chrome plugin. Plugins have a few annoying scope restrictions. Basically this observer exists in an entirely different scope than the window / functions that create the div. – Don P Aug 10 '16 at 18:54
  • 1
    The obvious solution is the only one, unless the site emits some kind of event prior to adding that node. – wOxxOm Aug 10 '16 at 19:02
  • `Refer this answer how I fixed it` https://stackoverflow.com/questions/61212681/how-to-add-event-listener-for-onclick-popup-in-javascript – GnanaJeyam Apr 15 '20 at 11:50

1 Answers1

102

Only an existing node can be observed.

But don't worry, since getElementById is insanely fast compared to enumeration of all mutations' added nodes, waiting for the element to appear won't be taxing at all as you will see in Devtools -> Profiler panel.

function waitForAddedNode(params) {
    new MutationObserver(function(mutations) {
        var el = document.getElementById(params.id);
        if (el) {
            this.disconnect();
            params.done(el);
        }
    }).observe(params.parent || document, {
        subtree: !!params.recursive || !params.parent,
        childList: true,
    });
}

Usage:

waitForAddedNode({
    id: 'message',
    parent: document.querySelector('.container'),
    recursive: false,
    done: function(el) {
        console.log(el);
    }
});

Always use the devtools profiler and try to make your observer callback consume less than 1% of CPU time.

  • Whenever possible observe direct parents of a future node (subtree: false)
  • Use getElementById, getElementsByTagName and getElementsByClassName inside MutationObserver callback, avoid querySelector and especially the extremely slow querySelectorAll.
  • If querySelectorAll is absolutely unavoidable inside MutationObserver callback, first perform the querySelector check, on the average such combo will be much faster.
  • Don't use Array methods like forEach, filter, etc. that require callbacks inside MutationObserver callback because in Javascript function invocation is an expensive operation compared to the classic for (var i=0 ....) loop, and MutationObserver callback may fire 100 times per second with dozens, hundreds or thousands of addedNodes in each batch of mutations on complex modern pages.
  • Don't use the slow ES2015 loops like for (v of something) inside MutationObserver callback unless you transcompile and the resultant code runs as fast as the classic for loop.
wOxxOm
  • 65,848
  • 11
  • 132
  • 136
  • 7
    Welp, that was one of the most in depth (with regards to perf) answers I've ever gotten on here. Thank you! – Don P Aug 10 '16 at 19:33
  • 2
    An ingenious approach as you're not relying on the mutations properties (where I have had difficulty) but just checking the required element exists each time. A bit like polling with a timeout. Relies on element having an id though... – josef Jul 31 '19 at 14:16