How to wait until an element exists with JavaScript?

12,432

Solution 1

I finally made it with MutationObserverinterface in an easy way instead of with promises.

var handler = {
    makeThings: 0,
    otherStuff: 0
};
var globalHandler = new Proxy(handler, {
    set: function(obj, prop, value) {
        obj[prop] = value
        if (prop == "makeThings") {
            var observer = new MutationObserver(function(mutations) {
                if ($("p").length) {
                    console.log("Exist, lets do something");
                    observer.disconnect();
                }
            });
            // start observing
            observer.observe(document.body, {
                childList: true,
                subtree: true
            });
        }
        return true;
    }
});

$(document).ready(function() {
    $("button").on("click", function() {
        $("p").remove();
        globalHandler.makeThings = 1;
        //This element comes with ajax but I use a setTimeout for this example
        setTimeout(function() {
            $("#newContent").append("<p>Ajax element</p>");
        }, 2000);
    });
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<body>
  <button>New content</button>
  <div id="newContent"></div>
</body>

Solution 2

Do not wait. Rather subscribe for notification of a change in the target element.

The API to utilise for listening to changes in the DOM tree is the MutationObserver.

The MutationObserver interface provides the ability to watch for changes being made to the DOM tree. It is designed as a replacement for the older Mutation Events feature which was part of the DOM3 Events specification.

Use it to observe change in an element as follows:

// You selected `$("p")` in your snippet, suggesting you're watching for the inclusion of 'any' `p` element.
// Therefore we'll watch the `body` element in this example
const targetNode = document.body;

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

// Callback function to execute when mutations are observed
const callback = function(mutationsList, observer) {
    for(let mutation of mutationsList) {

        if ( mutation.type === "childList" ) {
            continue;
        }

        const addedNodes = Array.from( mutation.addedNodes) ;

        if ( addedNodes && addedNodes.some( node => node.nodeName === "P" ) ) {
            observer.disconnect();

            console.log("The element finally exist and we execute code");
        }
    }
};

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

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

Solution 3

rxjs can highly simplify what you are trying to do. A very basic implementation, using only a Subject and subscription:

const {
  Subject
} = rxjs;

const sub = new Subject();

sub.subscribe(e => {
  console.log(`received data ${e}`);
  // do your thing
});

// simulate something async
setTimeout(() => {
  sub.next('foo');
}, 1000);
<script src="https://unpkg.com/[email protected]/bundles/rxjs.umd.min.js"></script>
Share:
12,432
SilverSurfer
Author by

SilverSurfer

Updated on July 25, 2022

Comments

  • SilverSurfer
    SilverSurfer almost 2 years

    I'm working with a proxy object where I detect a object value change and then load new content via AJAX, I use a setInterval function to wait until a the element that comes in the AJAX request exist and then execute a piece of code. I'm doing in this way because my case requires it. I made a short snippet example:

    var handler = {
        makeThings: 0,
        otherStuff: 0
    };
    var globalHandler = new Proxy(handler, {
        set: function(obj, prop, value) {
            obj[prop] = value
            if (prop == "makeThings") {
                var clearTimeSearchProxy = setInterval(function() {
                    if ($("p").length) {
                        console.log("The element finally exist and we execute code");
                        clearTimeout(clearTimeSearchProxy);
                    }
                }, 100);
            }
            return true;
        }
    });
    
    $(document).ready(function() {
        $("button").on("click", function() {
            globalHandler.makeThings = 1;
            //This element comes with ajax but I use a setTimeout for this example
            setTimeout(function() {
                $("#newContent").append("<p>Ajax element</p>");
            }, 2000);
        });
    });
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <body>
      <button>New content</button>
      <div id="newContent"></div>
    </body>

    Now I'm wondering about how to improve the code in a cleaner, efficient and elegant way. I was thinking of using promises instead of setInterval to execute a code when the element that comes via AJAX exists in the DOM.

    How can I make it work? Should I use other JavaScript functionality for this case instead of promises? I'm stuck with the promise to achieve what I need, this what I have tried so far.

    var handler = {
        makeThings: 0,
        otherStuff: 0
    };
    var globalHandler = new Proxy(handler, {
        set: function(obj, prop, value) {
            obj[prop] = value
            if (prop == "makeThings") {
                var myFirstPromise = new Promise((resolve, reject) => {
                    if ($("p").length) {
                        resolve("Exist");
                    } else {
                        reject("It doesnt exist.");
                    }
                });
    
                myFirstPromise.then((data) => {
                    console.log("Done " + data);
                }).catch((reason) => {
                    console.log("Handle rejected promise: " + reason);
                });
            }
            return true;
        }
    });
    
    $(document).ready(function() {
        $("button").on("click", function() {
            globalHandler.makeThings = 1;
            //This element comes with ajax but I use a setTimeout for this example
            setTimeout(function() {
                $("#newContent").append("<p>Ajax element</p>");
            }, 2000);
        });
    });
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <body>
      <button>New content</button>
      <div id="newContent"></div>
    </body>

  • jared
    jared about 3 years
    Is it necessary to set 'childList' to true in the config and then skip that mutation type in the observer callback?
  • Igwe Kalu
    Igwe Kalu about 3 years
    If you're indeed convinced that the config is strictly set up for observation of child addition/removal then you ought not check the mutation type.
  • SilverSurfer
    SilverSurfer almost 2 years
    If someone need a very simple and readable example of MutationObserver you can check this answer stackoverflow.com/a/57395241/6542186