Gin Template Variables
When building web applications with Gin, templates are essential for creating dynamic HTML pages. One of the most powerful features of templates is the ability to use variables, which allows you to display dynamic data in your web pages. In this tutorial, we'll explore how to use variables in Gin templates effectively.
Introduction to Template Variables
Template variables in Gin are placeholders within your HTML template files that get replaced with actual values when the template is rendered. These variables allow you to create dynamic web pages by injecting data from your Go application into your HTML templates.
Gin uses Go's standard html/template
package under the hood, so the variable syntax and features are consistent with Go's templating system.
Basic Variable Syntax
In Gin templates, you access variables using double curly braces: {{ .VariableName }}
. The dot (.
) notation refers to the current context, which is the data you pass to your template.
Getting Started with Variables
Let's start by creating a simple example to show how variables work in Gin templates.
Setting Up Your Gin Application
First, let's set up a basic Gin application with templates:
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
router := gin.Default()
// Load templates from the "templates" folder
router.LoadHTMLGlob("templates/*")
router.GET("/", func(c *gin.Context) {
// Passing a single variable to the template
c.HTML(http.StatusOK, "index.html", gin.H{
"title": "Welcome to Gin Templates",
})
})
router.Run(":8080")
}
Now, let's create a simple template file templates/index.html
:
<!DOCTYPE html>
<html>
<head>
<title>{{ .title }}</title>
</head>
<body>
<h1>{{ .title }}</h1>
<p>This page demonstrates how to use variables in Gin templates.</p>
</body>
</html>
When you run this application and visit http://localhost:8080
, you'll see:
Output:
- The page title will be "Welcome to Gin Templates"
- An H1 heading will display "Welcome to Gin Templates"
Passing Multiple Variables to Templates
You can pass multiple variables to templates using a gin.H
map or a struct.
Using a gin.H Map
router.GET("/about", func(c *gin.Context) {
c.HTML(http.StatusOK, "about.html", gin.H{
"title": "About Us",
"company": "Go Web Developers",
"year": 2023,
"team": []string{"Alice", "Bob", "Charlie"},
})
})
Using a Custom Struct
type PageData struct {
Title string
Company string
Year int
Team []string
}
router.GET("/about-struct", func(c *gin.Context) {
data := PageData{
Title: "About Us",
Company: "Go Web Developers",
Year: 2023,
Team: []string{"Alice", "Bob", "Charlie"},
}
c.HTML(http.StatusOK, "about.html", data)
})
And here's how you'd use these variables in your templates/about.html
file:
<!DOCTYPE html>
<html>
<head>
<title>{{ .Title }}</title>
</head>
<body>
<h1>{{ .Title }}</h1>
<p>Welcome to {{ .Company }}!</p>
<p>Established in {{ .Year }}</p>
<h2>Our Team</h2>
<ul>
{{ range .Team }}
<li>{{ . }}</li>
{{ end }}
</ul>
</body>
</html>
Variable Types in Templates
Gin templates can handle various types of variables:
Strings
<h1>{{ .Title }}</h1>
Numbers
<p>Founded {{ .Year }}</p>
<p>We have {{ .EmployeeCount }} employees</p>
Booleans
{{ if .IsActive }}
<span class="status active">Active</span>
{{ else }}
<span class="status inactive">Inactive</span>
{{ end }}
Slices/Arrays
<ul>
{{ range .Items }}
<li>{{ . }}</li>
{{ end }}
</ul>
Maps
<dl>
{{ range $key, $value := .Attributes }}
<dt>{{ $key }}</dt>
<dd>{{ $value }}</dd>
{{ end }}
</dl>
Nested Structs
<div class="user-info">
<h3>{{ .User.Name }}</h3>
<p>Email: {{ .User.Email }}</p>
<p>Role: {{ .User.Role }}</p>
</div>
Variable Declaration in Templates
You can also declare local variables within your templates using the $
symbol:
{{ $name := "John" }}
<p>Hello, {{ $name }}!</p>
{{ $total := 0 }}
{{ range .Items }}
{{ $total = add $total .Price }}
{{ end }}
<p>Total price: ${{ $total }}</p>
Note: For the add
function to work, you would need to register it as a custom template function.
Conditional Rendering with Variables
Variables are particularly useful when combined with conditionals:
{{ if .User }}
<p>Welcome back, {{ .User.Name }}!</p>
{{ else }}
<p>Please <a href="/login">log in</a> to continue.</p>
{{ end }}
{{ if eq .Role "admin" }}
<div class="admin-panel">
<!-- Admin-only content -->
</div>
{{ end }}
Looping with Variables
Gin templates allow you to iterate through slices, arrays, and maps:
<!-- Basic range loop -->
<ul>
{{ range .Products }}
<li>{{ .Name }} - ${{ .Price }}</li>
{{ end }}
</ul>
<!-- Range with index -->
<table>
{{ range $index, $product := .Products }}
<tr class="{{ if even $index }}even-row{{ else }}odd-row{{ end }}">
<td>{{ $index }}</td>
<td>{{ $product.Name }}</td>
<td>${{ $product.Price }}</td>
</tr>
{{ end }}
</table>
Real-World Example: Product Listing Page
Let's build a more complex example of a product listing page that demonstrates many of these concepts:
Go code:
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
// Product represents a product item
type Product struct {
ID int
Name string
Description string
Price float64
InStock bool
Categories []string
}
// PageData holds all the data for our page
type PageData struct {
Title string
User *User
Products []Product
ShowFilters bool
}
// User represents a logged-in user
type User struct {
Name string
Email string
IsAdmin bool
}
func main() {
router := gin.Default()
router.LoadHTMLGlob("templates/*")
// Add custom template functions
router.SetFuncMap(template.FuncMap{
"formatPrice": func(price float64) string {
return fmt.Sprintf("$%.2f", price)
},
})
router.GET("/products", func(c *gin.Context) {
// Sample data
products := []Product{
{1, "Laptop", "Powerful laptop for developers", 1299.99, true, []string{"Electronics", "Computers"}},
{2, "Headphones", "Noise-cancelling headphones", 249.99, true, []string{"Electronics", "Audio"}},
{3, "Mouse", "Ergonomic wireless mouse", 49.99, false, []string{"Electronics", "Accessories"}},
}
// Current user (could come from session or JWT)
user := &User{
Name: "Jane Smith",
Email: "[email protected]",
IsAdmin: true,
}
data := PageData{
Title: "Our Products",
User: user,
Products: products,
ShowFilters: true,
}
c.HTML(http.StatusOK, "products.html", data)
})
router.Run(":8080")
}
Template code (products.html):
<!DOCTYPE html>
<html>
<head>
<title>{{ .Title }}</title>
<style>
.out-of-stock { color: red; }
.admin-control { background-color: #ffeecc; padding: 5px; }
</style>
</head>
<body>
<header>
<h1>{{ .Title }}</h1>
{{ if .User }}
<div class="user-welcome">
Welcome, {{ .User.Name }}!
{{ if .User.IsAdmin }}
<span class="admin-badge">Admin</span>
{{ end }}
</div>
{{ else }}
<a href="/login">Log In</a>
{{ end }}
</header>
{{ if .ShowFilters }}
<div class="filters">
<h3>Filter Products</h3>
<!-- Filter UI would go here -->
</div>
{{ end }}
<main>
<div class="products">
{{ if .Products }}
<ul class="product-list">
{{ range $index, $product := .Products }}
<li id="product-{{ $product.ID }}">
<h2>{{ $product.Name }}</h2>
<p>{{ $product.Description }}</p>
{{ if $product.InStock }}
<span class="stock-status">In Stock</span>
{{ else }}
<span class="stock-status out-of-stock">Out of Stock</span>
{{ end }}
<p class="price">{{ formatPrice $product.Price }}</p>
<div class="categories">
{{ range $product.Categories }}
<span class="category">{{ . }}</span>
{{ end }}
</div>
{{ if $.User.IsAdmin }}
<div class="admin-control">
<button>Edit Product</button>
<button>Delete Product</button>
</div>
{{ end }}
</li>
{{ end }}
</ul>
{{ else }}
<p>No products found.</p>
{{ end }}
</div>
</main>
<footer>
<p>© {{ .Year }} Our Store. All rights reserved.</p>
</footer>
</body>
</html>
In this example, we've demonstrated:
- Variable access with
.Title
,.User
, etc. - Conditionals with
if .User
andif .ShowFilters
- Looping with
range
- Nested property access with
.User.Name
- Custom template functions with
formatPrice
- Scope and context with
$.User.IsAdmin
inside a range loop
Common Errors with Template Variables
Here are some common mistakes and how to avoid them:
1. Forgetting the Dot Prefix
<!-- Incorrect -->
<h1>{{ Title }}</h1>
<!-- Correct -->
<h1>{{ .Title }}</h1>
2. Trying to Access Unexported Fields
Go's templating system can only access exported fields (starting with uppercase) in structs:
// This field won't be accessible in templates
type User struct {
name string // lowercase, unexported
}
// Fix: Make it exported
type User struct {
Name string // Uppercase, exported
}
3. Accessing Fields that Don't Exist
If you try to access a field that doesn't exist, the template will silently return an empty value rather than causing an error:
<!-- If .NonExistentField doesn't exist, this will just be empty -->
<div>{{ .NonExistentField }}</div>
4. Incorrect Context in Range Loops
Inside a range
loop, the dot .
context changes to represent the current item:
<!-- Incorrect: using the wrong context -->
{{ range .Items }}
<li>{{ .Items.Name }}</li>
{{ end }}
<!-- Correct: the dot is now the current item -->
{{ range .Items }}
<li>{{ .Name }}</li>
{{ end }}
<!-- Accessing the outer context using $ -->
{{ range .Items }}
<li>{{ .Name }} - from {{ $.StoreName }}</li>
{{ end }}
Summary
Gin template variables provide a powerful way to create dynamic web pages in your Go applications. Here's what we've learned:
- Variables are accessed using the
{{ .VariableName }}
syntax - You can pass data to templates using
gin.H
maps or custom structs - Templates support various data types including strings, numbers, booleans, slices, maps, and nested structs
- You can declare local variables within templates using the
$
symbol - Variables can be combined with conditionals, loops, and custom functions for complex template logic
- The context (
.
) changes within range loops, but you can access the original context using$
By mastering Gin template variables, you can create sophisticated, data-driven web pages that dynamically adapt to your application's state.
Additional Resources and Exercises
Resources
Exercises
-
Basic Exercise: Create a page that displays a user profile with name, email, and joined date. Pass this information as variables to the template.
-
Intermediate Exercise: Create a blog post listing page that shows post titles, excerpts, publication dates, and author information. Use nested objects for author data.
-
Advanced Exercise: Build a shopping cart page that displays items, quantities, individual prices, and a calculated total. Implement the ability to show/hide sections based on whether the cart is empty or not.
-
Challenge: Create a dashboard that displays different widgets based on the user's role (admin, editor, viewer). Each widget should render different data using appropriate template variables and conditionals.
Remember to test your templates with different data conditions to ensure they handle all scenarios gracefully!
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)