Templates state

Templates state

Templates state refers to the data the templates can access during their rendering phase. Edge provides four layers to pass or define the template state.

Since Edge templates are rendered on the server, the data is never shared with the client-side runtime.

Globals

The globals refer to the state defined using the edge.global method. Globals are available to all the templates, including components.

For example, you can use globals to share the website config with all the templates.

edge.global('config', {
colorScheme: 'dark',
menu: [],
socialLinks: [],
})
<html class="{{ config.colorScheme }}">
<head>
<head>
<body>
<header>
@each(item in config.menu)
@end
</header>
<footer>
@each(link in config.socialLinks)
@end
</footer>
</body>
</html>

You can also share classes, functions, and almost every JavaScript data-type as global properties.

edge.global('findUser', async function (id) {
return User.findById(id)
})
@let(user = await findUser(1))
{{ user.username }}

Locals

The locals are similar to the global state but isolated between multiple render calls.

In the following example, we use the edge.createRenderer method to create multiple children instances of edge and share separate data objects with them.

The data will be available globally to all the templates included as partials or components, but not with the isolated children instances.

const templ1 = edge.createRenderer()
const templ2 = edge.createRenderer()
templ1.share({
url: '/posts',
})
templ2.share({
url: '/posts/1',
})
await templ1.renderRaw('{{ url }}') // /posts
await templ2.renderRaw('{{ url }}') // /posts/1

Why use locals?

You might be thinking, why create a new isolated instance and use the .share() method to share locals with a template when you can pass the data during the .render() method call?

Why this?

const view = edge.createRenderer().share({
url: req.url
})
await view.render('template-path')

And not this?

await edge.render('template-path', {
url: req.url
})

Let's use a concrete example and understand when locals can be helpful.

Imagine you use Express.js and Edge together and want to share data with a template using middleware. Also, the shared data should be isolated between concurrent requests handled by your application.

You can create a new instance of the Edge renderer for each request and use the share method to share global data isolated between multiple HTTP requests.

app.use(function (req, res) {
res.view = edge.createRenderer()
})
app.use(function (req, res) {
res.view.share({
url: req.url
})
})
app.use(function (req, res) {
res.view.share({
user: req.auth.user
})
})
// Finally render a template
app.get('/posts', async (req, res) => {
const html = await res.view.render('posts')
res.send(html)
})

Rendering data object

The rendering data refers to the data object passed when calling the edge.render method. The rendering data is not shared with the components used by a template.

const renderingData = {}
await edge.render('template-path', renderingData)

Inline variables

Inline variables are defined within the template as let variables. You can define inline variables using the @let tag and re-assign them new values using the @assign tag.

@let(config = await loadConfig())
{{ config.someKey }}

The scope of inline variables is similar to a let variable in JavaScript. Let's consider the following example, in which we mutate an inline variable inside an each loop.

{{-- Define variable --}}
@let(total = 0)
<ul>
@each(item in items)
{{-- Re-assign it a new value --}}
@assign(total = total + item.price)
<li> {{ item.name }} = {{ item.price }} </li>
@end
<li> Gross total = {{ total }} </li>
<ul>

Data layers and their scope

The final template state is a merged copy of all the layers created using Object.assign. Therefore, the layer with top most priority will overwrite the values from the previous layers.

Pseudocode
const finalState = Object.assign(
{},
globals,
locals,
renderingData
)
NameShared with components?Isolated?
Globals
Locals
Rendering data object
Inline variables