Skip to content

Use attributes instead of properties #259

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion properties-vs-attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,14 @@ Notice that the attribute is called `class` but the property is called `classNam

It is actually a bit crazier than that though. **Sometimes an attribute exists, but there is no corresponding property.** For example, as of this writing the `webkit-playsinline` can be added with `setAttribute`, but there is no corresponding property. And with SVG, you cannot use properties at all, you must to use `setAttributeNS` for everything.

With all the corner cases here, it makes sense to have access to both approaches.
With all the corner cases here, it makes sense to have access to both approaches.

The functions in `Html.Attributes` are generally implemented using attributes (except for a few things that _have_ to be properties). Using attributes have a couple of benefits for standard attributes:

1. It’s possible to remove an attribute, but not always possible to remove a property. For example, the HTML `<a>` results in a link without any `href`. The specification defines that as a link placeholder, and a use case is a menu where the current page does not need to link to itself. But `.href` is actually set to the empty string (`""`) on the DOM node. The HTML `<a href="/about">` results in a link _with_ a `href`. If you have just `<a>`, you can either do `.href = "/about"` or `.setAttribute("href", "/about")` to set the `href`. But how do you remove the attribute again? If you try `.href = ""`, that actually results in `<a href="">` (which is not the same as the `href` attribute being missing), while `.removeAttribute("href")` does result in `<a>`.

2. When initializing an Elm app, you give it a DOM node to render into, or the `<body>` node is used. What happens if the node to render into isn’t empty? Elm then _virtualizes_ the DOM into virtual DOM. If the DOM nodes match what your app’s first render, the virtual DOM diffing won’t find any changes to make. This allows for server side rendering HTML, and then have Elm take over that content and avoiding lots of work at startup. Let’s take `<a href="/about">` as an example again. When virtualizing, Elm has to make a guess: Did you use `property "href" (Json.Encode.string "/about")` or `attribute "href" "/about"` in your Elm code? If we guess wrong, the first render will do an unnecessary DOM update, and if we guess right the first render won’t do anything as expected. Another complication is that attributes and properties don’t always have the same name, as mentioned above. When virtualizing DOM nodes, it’s possible to loop over all _attributes_ that are set, but it’s not possible to loop over properties. For `<div class="user-info">`, a loop over attributes would get that `class` attribute, but if `Html.Attributes.class` is implemented with a property, we would have to translate `class` into `className` with a lookup table. By primarily using attributes in `Html.Attributes`, we don’t need a lookup table (except for a couple of edge cases).

3. Some properties are read only – if you try to assign them an error is thrown. The most notable example of this is trying to do `.className = "my-class"` on an SVG element. That throws an error, while `.setAttribute("class", "my-class")` works. In Elm, both `Html msg` and `Svg msg` are type aliases for the same virtual DOM node type, so you can mix functions from elm/html and elm/svg without getting type errors. `Html.Attributes.class` used to be implemented by setting the `className` property, and a common mistake was accidentally using using `Html.Attributes.class` on an SVG element, instead of `Svg.Attributes.class` (which is implemented by setting the `class` attribute), which would then cause hard to debug runtime errors. By setting the `class` attribute in `Html.Attributes.class` this issue is avoided.

4. Attributes are easier to diff. If we take the example with the link again, if you do `.href = "/about"`, then what is the value of `.href` afterwards? You might think it’s `"/about"`, but it’s actually `"https://example.com/about"` (if the code is running on `https://example.com`). `.href` returns the _full_ URL. If you set it to a non-full URL, the browser resolves it to the full URL. This is important because some properties change automatically through user interactions. For example, `.value` of a text input updates automatically as the user types into it. But if you have code like `Html.input [ Html.Attributes.value "hardcoded" ]` you would expect the input to always say “hardcoded”. If the virtual DOM diffing compares the old and new virtual DOM, it’ll see that both of them say that `value` should be `"hardcoded"` and decided that no update is needed. But in reality, the input displays whatever the user has typed. For this reason, it sounds like a good idea to instead diff the new virtual DOM against the actual DOM node when it comes to properties. The downside is the `href` example, though: If the virtual DOM says that `href` should be `"/about"` it will never be equal to the `.href` property on the DOM node, since it’s `"https://example.com/about"`, which would result in us setting `.href = "/about"` on _every_ render. Again, this is a reason to prefer attributes where possible. `value` is a good example of where a _property_ is used on purpose: We _need_ to set it on every render to make sure that it is set to the desired value.
Loading