Let's take a look at the tag we insert into the page.
The tag has attributes. These attributes may be specific to each component. For example, the KoiLabel component has a value attribute.
These attributes are somewhat different from the attributes of standard HTML tags. We are used to the idea that if we insert a button onto the page, for example, its value will become the text displayed on the button.
In the case of components from the KoiCom library, this is not quite the same. The values of the attributes are treated as data that the component can use for its operations. The component can convert this data into display data, and the displayed data might look completely different from what is specified in the attribute.
A second important point is that if we change the value of an object's property for a standard button via code, the value of the button’s attribute will change as well. Conversely, if we change the value of the attribute, the property’s value will also change. In other words, the two entities (the attribute value and the object property value) are considered as one and the same.
Honestly, I don’t know why this was implemented this way.
Furthermore, we can easily confirm that this implementation is flawed, and we should not treat the attribute value and the property value as the same thing. We can verify this by running the following code:
We passed an array into the property and expected to get the value of the array at index 1. However, when setting the property, the value is unexpectedly converted into a string, and we get the character at index 1 of the string, which is a comma. The unexpected nature of this conversion led me to question how attributes should actually be used.
Default values
In the KoiCom library, attributes are used to set default values. This means that changing an attribute after the component has been connected to the DOM should not affect anything, and changing the component's property value should not impact the attribute.
Furthermore, because an attribute can only have a string value, while a component's property can hold a value of any type, the attribute’s value is converted to the property’s value when the component is connected to the DOM. We will discuss this in more detail in the Schema section.
Service attributes
Naturally, since each component performs a specific task, it requires distinct attributes for setting service values and separate attributes for defining the data values the component will work with.
Service attributes include, as with standard components, attributes like id, class, style, and so on. These attributes can be modified during program execution, and such modifications can affect the component's appearance. For example, if we need to hide a component, we can change its CSS display style to none. Since working with standard attributes is well-understood and familiar, the KoiCom library does not alter their behavior.
However, the KoiCom library also introduces several custom service attributes. These attributes are used to define default values for the component’s properties and are applied exclusively during the component’s construction. The component’s subsequent lifecycle neither depends on nor affects these attributes. Below are two of the most commonly used service attributes:
- debug_mode
- provider_id
The debug_mode attribute is designed to enable debug mode in the component, logging the names of executed methods and the values of certain variables to the console as they are processed. This feature is invaluable for tracing the sequence of method calls and understanding the interactions between multiple components.
The provider_id attribute points to the component provider (an external data source for the component) and will be explored further in the Connectors section.
These attributes are directly associated with component properties. For example, the debug_mode attribute is tied to the _debug_mode property, and the provider_id attribute corresponds to the _connector property. Indeed, service attributes serve to define the default values for a component's class properties.
When creating a new component, you may need to define custom properties. These properties should typically be protected, indicated by a leading underscore in their names. A common consideration is determining the initial value of these properties. If the initial value needs to depend on the conditions under which the component’s tag is rendered in the HTML code, you can create a custom service attribute for the property.
It’s important to ensure that attribute names do not conflict with existing ones. Using a naming prefix is a common approach to avoid clashes. However, as seen with the debug_mode and provider_id attributes, these specific names lack prefixes. This omission is intentional, highlighting the unique and exceptional nature of the properties they represent.
To define a custom service attribute, you must override the _onConstructed method, as default values for service attributes are established in the component's constructor.
Since an attribute's value can only be a string, converting it into a property value may involve data transformation. For this purpose, you’ll need to implement a protected method to handle the conversion.
When adding a tag to HTML, it’s easy to forget to define an attribute. For this reason, the _prepareMyAttributeFromAttributes method should account for such scenarios. If an attribute is omitted from the tag, the getAttribute method will return null.
Data conversion might involve exceptional cases. The conversion method should handle these appropriately. Since attribute assignment errors fall under the developer’s responsibility, such errors should not be suppressed. Instead, critical exceptions should be thrown.
Of course, like any homo pigrus, I don't personally use the code outlined above, but feel free to contribute and create something more elegant.
The attribute you define must be explicitly mentioned in the component’s getTag method. Be sure to document it there so that users of the component in HTML don’t have to read through your entire codebase to figure out which attributes are available.
Your service attribute is now almost complete. Only one step remains. Typically, a component property is tied to some functionality. It doesn’t stand alone but is usually part of a group of related methods. As discussed in the Composition section, the existence of such a method group might indicate that composition should be utilized to create the component. Reflect on whether the _prepareMyAttributeFromAttributes method should be included in a composable method group.
Data Attributes
Now, let’s explore the use of attributes as component data and clarify the distinction between service attributes and data attributes.
For instance, imagine we’re tasked with displaying a monetary amount. We could use the KoiLabel component as a base class for our new component. In this component, we aim to show the amount alongside its currency. The monetary value is clearly data — there’s little debate about that. However, the currency name often sparks questions.
According to the KoiCom library, the currency name, when passed via an attribute, is also considered data. It gets concatenated with the monetary value and rendered at the appropriate spot in the DOM. The component processes both the currency name and the monetary value identically — it transforms them for display.
What about displaying the text in different colors? Would this be a service attribute? No, that’s still data. The transformation code for converting the monetary value into a displayable format would likely translate the color into a CSS class name and generate HTML using that class. Thus, the color qualifies as data involved in the transformation.
So, what constitutes a service attribute? Let’s say the component requires an API call to fetch data for display, using a URL specified in the tag’s attribute. This URL is a service attribute because it doesn’t participate in the data transformation process. Instead, it becomes a property of the component used to retrieve data.
Let’s say your component can render a function graph using either the ChartJS library or the Highcharts library. You could create two distinct components, each with its own tag in HTML. Alternatively, you could create a single component with an attribute specifying which library to use. In this case, the attribute would be classified as a service attribute.
The line between service attributes and data attributes is fine. Here’s how I draw the distinction: a service attribute configures or modifies the component’s behavior, whereas the component itself is responsible for transforming the data.
When you define attributes in a tag, there’s no visible distinction between service attributes and data attributes — they’re both written as strings. No naming rules explicitly separate one type from the other. Nevertheless, it’s important to clearly delineate them during development. In the component’s code, service attributes are represented as properties of the component, while data attributes are managed by the component’s internal data object.
In the Schema section, we’ll explore how components interact with data and how to define data attributes properly.