Repetition & Lists

An area where templates shine is dealing with repeating parts of the content. Markup template already handles Arrays of content for you, but it goes further to support you with even more optimal and simpler way to deal with things that repeat.

As mentioned above, Markup already renders array of contents for you:

const todos = [
  "Go shopping",
  "Call the car salesman",
  "Register for next semester classes"
]

const renderTodo = todo => html`<li class="todo-item">${todo}</li>`

html`<ul>${todos.map(renderTodo)}</ul>`

The above example will correctly render the list just fine and all that was needed in the template was a single line.

Using the natively supported array rendering of the template is perfect for static lists but not so much if the list content or size keeps changing. Fortunately, there is a helper for that.

repeat helper

The repeat helper is perfect for lists either static or dynamic.

We can change the above example with the repeat helper and all renders the same.

const todos = [
  "Go shopping",
  "Call the car salesman",
  "Register for next semester classes"
]

const renderTodo = todo => html`<li class="todo-item">${todo}</li>`

html`<ul>${repeat(todos, renderTodo)}</ul>`

The repeat helper takes 3 arguments and the third one is optional:

repeat(COUNT_OR_ARRAY, ITEM_RENDERER, ?WHEN_EMPTY_RENDERER):

  • COUNT_OR_ARRAY: You can specify a number of times you need something to repeat or an array.
  • ITEM_RENDERER: A function that will be called for every item and must return what to render. If a number was specified as the first argument, this function will get called with a number value and the index, otherwise, it will get called with the item in the array and its index.
  • WHEN_EMPTY_RENDERER (optional): A function that will get called when the list is empty and must return something to render in that case. If nothing is provided, nothing will be rendered.

Why use the repeat helper?

The repeat helper will keep an internal cache keyed by the items in the list. This is done to only re-render the items that change and not the whole list every time there is a change.

This also means that if you have a repeated value associated with a template or DOM Node it will only appear once in the DOM.

html`${repeat([1, 3, 5, 3], (n) => `item ${n}`)}`
// renders: item 1 item 3 item 5 item 3

html`${repeat([1, 3, 5, 3], (n) => html`<span>item ${n}</span>`)}`
// renders: item 1 item 3 item 5

html`${repeat([1, 3, 5, 3], (n) => element('span', {
  textContent: `item ${n}`
}))}`
// renders: item 1 item 3 item 5

This is only true when you are trying to render Nodes, and it is not specific to Markup. You can't render the same nodes in multiple places. Because each template or node are mapped to the same value reference even when the repeat tries to render the whole list.

Cache Map
1 -> DOM Node(s) 1
3 -> DOM Node(s) 3
5 -> DOM Node(s) 5

The cache is updated whenever there is a change in the item reference or position in the list. But only the things that change will be updated in the cache map.