The objective and what I've tried
I'm trying to get a method to be able to wait until an element is already in the DOM. I've read a few articles about MutationObserver, and I got this method which should accomplish what I need:
const waitForElement = async (queryString) => {
return new Promise((resolve) => {
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
const nodes = Array.from(mutation.addedNodes);
nodes.forEach((node) => {
console.log('NODE CUSTOM', node);
if (node.matches && node.matches(queryString)) {
observer.disconnect();
resolve(node);
}
});
});
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
});
};
Then, I could simply use it this way:
await waitForElement('#id-of-element');
The problem
The thing is that it is actually not working as expected: the console.log only logs the "parents" elements, and if the element to be searched is deep in the tree, it seems not to log it (this is being used in a more complex application, so it may have to do with async calls and so on).
The question
However, I found that, in stead of going through arrays of mutations and nodes, I only need to see if the actual element is in the DOM, so I implemented this:
const waitForElement = async (queryString) => {
return new Promise((resolve) => {
let element;
const observer = new MutationObserver(() => {
element = document.querySelector(queryString);
if (element) {
observer.disconnect();
resolve(element);
}
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
});
};
This approach will just check if, after each mutation, the element is actually on the DOM using the querySelector method. It actually works (where the other one fails), and I find it easier to read and understand, and with less loops in between.
Is this a recommendable approach? Will it affect performance, or it will be just the same as the first approach?
Thank you!
A single query for a selector should be very, very, very fast, so I wouldn't expect this to be a problem, but it will depend a lot on the DOM you're using it in. Test your use cases to see if you find a performance problem.
I would at a minimum make it possible to specify where in the DOM to look, rather than looking through the entire thing on every change. That minimizes calls to your mutation observer, and minimizes the amount of searching it does when it's called. If a particular use case has to look through the entire thing, well, then it can use
documentElement, but I'd at least make it possible to avoid that. (There's also no reason to declareelementin a scope above the scope where it's actually used, and "query string" has a specific meaning in web programming that isn't what you're using it for here, so I'd just use "selector" or "selector string".)Here's an idea of how you might do that:
It would probably also make sense to optionally accept an
AbortSignalso you can stop waiting for the element, having it reject with some appropriate "cancelled" error.