Skip to main content

Templates

A template is a deferred block of components. The components inside a template are not created immediately — they are created each time the template is invoked.

Template Type​

The Template type represents a template. Parameters are declared by listing their types inside the parentheses.

Template() // no parameters
Template(String) // one String parameter
Template(String, Int) // two parameters

Accepting Templates as Properties​

A component accepts a template by declaring a @property of type Template(…).

import { VStack, Text } from ui

component Panel {
@property title: String
@property children: Template()

VStack {
Text(self.title)
self.children()
}
}

When a property is named children with type Template(), callers may use a trailing block:

var panel = Panel(title: "My Panel") {
Text("First item")
Text("Second item")
}

Parameterized Templates​

Templates can receive parameters, allowing the caller to use the passed values when declaring the children.

import { VStack, Text } from ui

component List {
@property data: [String]
@property item: Template(String)

VStack {
for text in self.data {
self.item(text)
}
}
}

export var main: Component = List(
data: ["Alpha", "Beta", "Gamma"],
item: template (text) {
Text(text)
}
)

Applying Modifiers to a Template Invocation​

A template invocation is an expression producing components, so it can take a with block like any component — self.children() with { … }, self.item(text) with { … }. See Applying Modifiers to Expressions.

Declarative Loops and Conditionals​

Inside a template body, for and if work declaratively — see Declarative Control Flow for full details.

No Variable Declarations​

A template body — and likewise the trailing block passed to a children property — contains component expressions only. Variables cannot be declared inside them.

import { VStack, Text } from ui

component Panel {
@property children: Template()

VStack {
self.children()
}
}

export var main: Component = Panel {
var label = "First item" // error — no variable declarations inside a trailing block
Text("First item")
}

Compute the value somewhere it is allowed and pass it in instead — as a template parameter, or as a property or state of a component:

import { VStack, Text } from ui

component List {
@property data: [String]
@property item: Template(String)

VStack {
for text in self.data {
self.item(text)
}
}
}

export var main: Component = List(
data: ["Alpha", "Beta"],
item: template (text) {
Text("Item: \{text}")
}
)

Templates vs Functions​

Both templates and functions can be passed as properties to components, but they have fundamentally different behaviour in reactive contexts.

When a function is called in a reactive expression — such as a component property or computed state — the reactive system records a dependency on every argument passed to it. Whenever any of those arguments change, the expression re-evaluates and the function is called again. If the function creates components, this means new component instances are created on every re-evaluation, which is expensive and leads to components being needlessly destroyed and recreated.

import { VStack } from ui

// Problematic: item is a function that creates a component.
// Calling item(text) makes the surrounding context depend on "text".
// Every time "text" changes, a new component is created.
component BadList {
@property data: [String]
@property item: (String) -> (Component)

VStack {
for text in self.data {
self.item(text) // new component instance on every change
}
}
}

Templates, by contrast, lazily forward their parameters. Calling a template does not make the reactive context depend on its arguments — the template itself is responsible for tracking what it needs internally. This means the same component instances are reused across re-evaluations, only updating the parts that actually changed.

import { VStack } from ui

// Correct: item is a Template. Calling self.item(text) does not
// create a dependency on "text" in the surrounding context.
component GoodList {
@property data: [String]
@property item: Template(String)

VStack {
for text in self.data {
self.item(text) // reuses existing component, updates internally
}
}
}

As a rule of thumb: use Template whenever the purpose is to produce components. Reserve function properties for callbacks and logic that does not create components.