Sometimes we have to expose properties and methods to our users. When we expose these properties and methods, we want to be careful about what we expose.
For example, imagine you have a clock. As a user, you don’t care about what goes inside the clock. You only care about two things:
You need to be able to read the time
You need to be able to set the time
If we’re the creators of this clock, we only need to expose the clock’s face (for reading time) and a dial (for setting time). We don’t need to expose the internals and gears and allow users to mess with them.
If we expose internals, users will be able to reach in and use things we don’t want them to use. When this happens, we say there’s tight coupling.
Coupling is simply a term that’s loosely tied to the amount of inter-connectivity between components. There’s no absolute measure for coupling, so everything is relative.
Tight coupling here means people can reach in and use the internals. Loose coupling means people can only use the APIs that are exposed.
Exposing the Modal’s properties
Our modals contains the following properties and methods:
isOpen
siblingElements
showSiblingElements
hideSiblingElements
open
close
Which methods should we expose?
If we want to expose everything, we can simply return the modal object from Modal. Users will get access to all 6 properties.
It’s not important to expose open because each Modal has a unique way of opening. This unique way of opening is built into each Modal type already.
For example, Timed modals opens automatically after a delay.
However, it is important to expose close because users may need this close method.
For example, the timed modal contains an Ok button. If you click the Ok button, you’ll expect the modal to close.
When we expose close, users can add an event listener to the Ok button and use the close method to close the modal.
const timedModal = Modal({
type: 'timed',
// Other properties
})
// Get the Ok button
const timedModalElement = document.querySelector('#timed-modal')
const timedModalOkButton = timedModalElement.querySelector('.modal__content').querySelector('button')
// Add event listener to Ok button
timedModalOkButton.addEventListener('click', _ => {
timedModal.close()
})
Exposing HTML Elements
I find it helpful to expose HTML Elements that are declared (or defined) inside my components.
For example, if we expose the modal’s contentElement, users won’t have to search for the content element (again) before they find the Ok button. They can use the element we expose.
Here’s how we can expose contentElement (along with other elements).
function BaseModal () {
// Declare Variables
const modal = {
// Exposes some HTML elements
modalElement,
contentElement,
overlayElement,
// Other methods
}
// Return modal
}
But this begets the original question: Are we exposing too many things?
Are we exposing too many things?
In an ideal scenario, we don’t want to expose ANY unnecessary properties or methods. We can achieve this. It just takes a bit more effort (like what we showed above).
There are three other ways you can do this:
Private by convention
True private members with closures
Creating an internals object
Private by convention
We can prepend a property or method with _ to signal to developers that this property or method is intended to be private. Unfortunately, this doesn’t stop users from using these “private” members.
True private members with closures
We can create true private members by using closures. One example here is trapFocus. We can create every method like trapFocus, but this creates a lot of declarations, which can make the component hard to read.
Compare these two and you’ll see:
// Version 1: With closures
function Component () {
function one () {
// ...
}
function two () {
// ...
}
const component = {
// ...
}
}
// Version 2: Current way with methods
function Component () {
const component = {
one () {
// ...
},
two () {
// ...
}
}
}
See this? The indentation makes the code easier to parse.
Since the indentation makes code easy to parse, I thought of third method.
Creating an internal object
I need to declare this upfront. I don’t see anyone doing this at all. But I want to share this as a possible way of structuring code.
Here, we create an internal object that stores all properties and methods for private variables.
function BaseComponent () {
const internal = {
one () {},
two () {}
}
const component = {
someMethod () {
internal.one()
}
}
}
All three methods are valid. Ultimately it boils down to personal and team preferences.
You can even expose the entire modal object without filtering properties too. I tend to do this for components that don’t affect many people. (If there’s a small impact, it doesn’t require as much detailed thought).
It’s all a factor of efficiency, necessity, and results. You want to create a balance for yourself. You don’t need to force everything to be “perfect” or “correct”.