Is there any way to declare constants when the DOMContentLoaded event triggers, that are accessible outside?
window.addEventListener('DOMContentLoaded', () => { const button = document.getElementById("button") }) someObject.on("EventThatWillOnlyOccurAfterDOMContentLoads", () => { console.log(button.innerHTML) // ReferenceError })
Advertisement
Answer
If the question is: “Is there any way to declare a constant variable without a value, and assign one to it later?“, then the answer is no.
Think of it again: after the declaration, a constant variable’s value must always be the same; that’s the point in creating constants.
If you were able to create such a variable, that wouldn’t really be constant. Look at this example:
const button //This would throw a SyntaxError but assume it doesn't window.addEventListener('DOMContentLoaded', () => { button = document.getElementById("button") }) someObject.on('EventThatMayOccurEitherBeforeOrAfterDOMContentLoaded', () => { //We are unsure what button is here: it might be a DOM node or undefined, or... what? console.log(button) })
So, you can’t create a constant in the global scope that way. But why do you want to declare it in the global scope at all?
If you know that the second event won’t be fired before DOMContentLoaded
, then just move its declaration inside, like this:
window.addEventListener('DOMContentLoaded', () => { const button = document.getElementById("button") someObject.on("EventThatWillOnlyOccurAfterDOMContentLoads", () => { console.log(button.innerHTML) // Works perfectly }) })
This approach is at least as good as the one you wanted, if not better:
Keeping all variables inside an event listener:
- Completely avoids the pollution of the global scope (like an IIFE, that some use)
- Makes your code run only after the page is loaded, so you don’t have to worry about inaccessible DOM elements.
However, if you are unable to move all code into DOMContentLoaded
(for example, because you want to listen for an event, that triggers before it), you have one more option: making use of ES6’s asynchronous structures, the so-called Promises.
By using them, your code can wait for the given event (in your case, DOMContentLoaded
), without having to move that code inside its listener, and will work even if the second event is emitted multiple times:
const button = new Promise(setButton => { window.addEventListener('DOMContentLoaded', () => { //Resolve the promise when we get the value: setButton(document.getElementById("button")) }) }) someObject.on('EventThatMayOccurEitherBeforeOrAfterDOMContentLoaded', () => { //This will wait for the promise to resolve if it hasn't done so yet: button.then(button => { console.log(button.innerHTML) }) })
This approach may seem more complicated, but using promises everywhere can simplify your life when your code becomes asynchronous.
Also note that this approach has its limits, for example, you can’t nest two of these promises (if you try to do so, you’ll find yourself in a scenario like the one you’ve asked about):
const button = new Promise(setButton => { //How to make this one global as well? const anotherButton = new Promise(setAnotherButton => { window.addEventListener('DOMContentLoaded', () => { setButton(document.getElementById("button")) setAnotherButton(document.getElementById("button2")) }) }) })
Instead, you can collect all DOM elements into a single object, and resolve your promise with it:
const DOMElements = new Promise(resolve => { window.addEventListener('DOMContentLoaded', () => { //Resolve the promise when we get the value: resolve(Object.freeze({ button: document.getElementById("button"), anotherButton: document.getElementById("button2") })) }) }) someObject.on('EventThatMayOccurEitherBeforeOrAfterDOMContentLoaded', () => { //Destructure button: button.then(({button}) => { console.log(button.innerHTML) }) })