Echo Template Inheritance
Template inheritance is a powerful feature in web development that allows you to build a base "skeleton" template with common elements that can be extended and customized by child templates. This approach promotes code reusability and a DRY (Don't Repeat Yourself) approach to building your views.
Introduction to Template Inheritance
When building web applications, you'll often have common elements that appear on multiple pages - like headers, footers, navigation menus, and basic layout structures. Rather than duplicating this HTML in every template, template inheritance lets you define these elements once in a "base" or "parent" template, then extend and modify specific sections in "child" templates.
In Echo, we can implement template inheritance using Go's built-in templating system, with some special considerations for the Echo framework.
How Template Inheritance Works in Echo
Echo works with Go's standard html/template
package, which supports template inheritance through a combination of defining and using named template blocks.
The process generally works like this:
- Create a base layout template with placeholder blocks
- Create child templates that extend the base template
- Child templates override specific blocks with their own content
- Register the templates with Echo
Let's learn through examples how to implement this pattern.
Creating a Base Layout Template
First, let's create a base layout template that will serve as the foundation for our pages:
{{/* layouts/base.html */}}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{{block "title" .}}Default Title{{end}}</title>
<link rel="stylesheet" href="/static/css/main.css">
{{block "css" .}}{{end}}
</head>
<body>
<header>
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
</header>
<main>
{{block "content" .}}
<p>Default content - this should be overridden</p>
{{end}}
</main>
<footer>
<p>© {{.Year}} My Echo Website</p>
</footer>
<script src="/static/js/main.js"></script>
{{block "js" .}}{{end}}
</body>
</html>
In this base template:
- We've defined several blocks:
title
,css
,content
, andjs
- Blocks have default content that will be used if not overridden
- The
.Year
variable is expected to be passed from our handler
Creating Child Templates
Now, let's create a child template that extends our base layout:
{{/* views/home.html */}}
{{define "title"}}Home Page | My Website{{end}}
{{define "content"}}
<div class="home-container">
<h1>Welcome to My Website</h1>
<p>This is the home page content that is specific to this view.</p>
{{if .User}}
<p>Hello, {{.User.Name}}!</p>
{{else}}
<p>Please <a href="/login">login</a> to access all features.</p>
{{end}}
<div class="featured-items">
<h2>Featured Items</h2>
<ul>
{{range .FeaturedItems}}
<li>{{.Title}} - {{.Description}}</li>
{{end}}
</ul>
</div>
</div>
{{end}}
{{define "css"}}
<link rel="stylesheet" href="/static/css/home.css">
{{end}}
{{define "js"}}
<script src="/static/js/home.js"></script>
{{end}}
This child template:
- Overrides the
title
block with a specific title - Provides custom
content
for the home page - Adds page-specific CSS and JavaScript files
Setting Up the Template Renderer in Echo
To use these templates in an Echo application, we need to set up a template renderer:
package main
import (
"html/template"
"io"
"time"
"github.com/labstack/echo/v4"
)
// Template renderer
type TemplateRenderer struct {
templates *template.Template
}
// Implement the Render method for echo.Renderer interface
func (t *TemplateRenderer) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
// Add global data available to all templates
if viewContext, isMap := data.(map[string]interface{}); isMap {
viewContext["Year"] = time.Now().Year()
}
return t.templates.ExecuteTemplate(w, name, data)
}
func main() {
e := echo.New()
// Load templates
renderer := &TemplateRenderer{
templates: template.Must(template.ParseGlob("views/*.html")),
}
// Register the base layout template
template.Must(renderer.templates.ParseGlob("layouts/*.html"))
// Set renderer
e.Renderer = renderer
// Routes
e.GET("/", handleHome)
// Start server
e.Start(":8080")
}
// Home page handler
func handleHome(c echo.Context) error {
data := map[string]interface{}{
"User": nil, // User would come from authentication context
"FeaturedItems": []struct {
Title string
Description string
}{
{"Item 1", "Description for item 1"},
{"Item 2", "Description for item 2"},
{"Item 3", "Description for item 3"},
},
}
return c.Render(200, "home.html", data)
}
Using Template Inheritance in Practice
To use this template inheritance system in practice, we'll need to:
- Make sure we're correctly calling the templates with the right names
- Understand how to pass data between templates
- Learn how to add more blocks as needed
Example: Adding Another Page
Let's add an about page that also uses our base layout:
{{/* views/about.html */}}
{{define "title"}}About Us | My Website{{end}}
{{define "content"}}
<div class="about-container">
<h1>About Our Company</h1>
<section class="our-story">
<h2>Our Story</h2>
<p>Founded in {{.FoundingYear}}, our company has been providing excellent service for over {{.YearsActive}} years.</p>
</section>
<section class="team">
<h2>Our Team</h2>
<div class="team-members">
{{range .TeamMembers}}
<div class="team-member">
<h3>{{.Name}}</h3>
<p class="title">{{.Title}}</p>
<p class="bio">{{.Bio}}</p>
</div>
{{end}}
</div>
</section>
</div>
{{end}}
{{define "css"}}
<link rel="stylesheet" href="/static/css/about.css">
{{end}}
And the corresponding handler:
// About page handler
func handleAbout(c echo.Context) error {
currentYear := time.Now().Year()
foundingYear := 2015
data := map[string]interface{}{
"FoundingYear": foundingYear,
"YearsActive": currentYear - foundingYear,
"TeamMembers": []struct {
Name string
Title string
Bio string
}{
{
"Jane Doe",
"CEO & Founder",
"Jane has over 15 years of industry experience.",
},
{
"John Smith",
"CTO",
"John leads our technical team with expertise in Go and web technologies.",
},
{
"Alex Johnson",
"Lead Developer",
"Alex specializes in building high-performance web applications.",
},
},
}
return c.Render(200, "about.html", data)
}
Nested Template Inheritance
You can also create multi-level template inheritance. For example, you might have:
- A base layout with the overall HTML structure
- A dashboard layout that extends the base but adds dashboard-specific elements
- Individual dashboard pages that extend the dashboard layout
Here's a simplified example of a dashboard layout:
{{/* layouts/dashboard.html */}}
{{define "dashboard-layout"}}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{{block "title" .}}Dashboard | My Website{{end}}</title>
<link rel="stylesheet" href="/static/css/main.css">
<link rel="stylesheet" href="/static/css/dashboard.css">
{{block "css" .}}{{end}}
</head>
<body>
<header>
<nav class="main-nav">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/dashboard">Dashboard</a></li>
<li><a href="/logout">Logout</a></li>
</ul>
</nav>
</header>
<div class="dashboard-container">
<aside class="sidebar">
<nav class="sidebar-nav">
<ul>
<li><a href="/dashboard">Overview</a></li>
<li><a href="/dashboard/analytics">Analytics</a></li>
<li><a href="/dashboard/settings">Settings</a></li>
</ul>
</nav>
</aside>
<main class="dashboard-content">
{{block "dashboard-content" .}}
<p>Default dashboard content</p>
{{end}}
</main>
</div>
<footer>
<p>© {{.Year}} My Echo Website</p>
</footer>
<script src="/static/js/main.js"></script>
<script src="/static/js/dashboard.js"></script>
{{block "js" .}}{{end}}
</body>
</html>
{{end}}
Then a specific dashboard page:
{{/* views/dashboard/analytics.html */}}
{{template "dashboard-layout" .}}
{{define "title"}}Analytics Dashboard | My Website{{end}}
{{define "dashboard-content"}}
<div class="analytics-container">
<h1>Analytics Dashboard</h1>
<div class="metrics-summary">
<div class="metric-card">
<h3>Total Users</h3>
<p class="metric-value">{{.Stats.TotalUsers}}</p>
</div>
<div class="metric-card">
<h3>Active Users</h3>
<p class="metric-value">{{.Stats.ActiveUsers}}</p>
</div>
<div class="metric-card">
<h3>Revenue</h3>
<p class="metric-value">${{.Stats.Revenue}}</p>
</div>
</div>
<div class="chart-container">
<!-- Charts would be rendered via JavaScript -->
<div id="user-growth-chart" data-metrics="{{.ChartData}}"></div>
</div>
</div>
{{end}}
{{define "js"}}
<script src="/static/js/charts.js"></script>
<script src="/static/js/analytics.js"></script>
{{end}}
Best Practices for Template Inheritance
- Keep your base templates simple: Focus on structure and common elements
- Name your blocks semantically: Use names that describe their purpose, not their location
- Provide sensible defaults: When possible, include default content in blocks
- Organize templates logically: Consider a directory structure that mirrors your template hierarchy
- Be consistent with data passing: Establish conventions for data structure
- Consider performance: Too many nested templates can impact rendering performance
Common Challenges and Solutions
Challenge: Templates Not Loading
If your templates aren't loading correctly, check:
- File paths are correct
- Template names match when rendering
- Template parsing occurs in the correct order (base templates first)
Challenge: Data Not Available in Templates
If data isn't showing up:
- Ensure you're passing data to the template
- Check that variable names match
- Verify the data structure matches what your template expects
Challenge: Multiple Templates Sharing Layouts
For larger applications with many templates:
- Consider automated template discovery
- Group templates by feature or section
- Create specialized base layouts for different sections
Summary
Template inheritance in Echo provides a powerful way to create DRY, maintainable templates for your web application. By establishing a hierarchy of templates - from base layouts to specific views - you can:
- Maintain consistent structure across your application
- Reduce duplication of common elements
- Easily update shared components in a single place
- Create specialized templates for different sections of your site
The combination of Go's template system with Echo's renderer interface gives you flexibility in implementing template inheritance that suits your application's needs.
Additional Resources
Exercises
- Basic Exercise: Create a base template and two views that extend it (home and contact pages)
- Intermediate Exercise: Implement a three-level template hierarchy (base → section → specific page)
- Advanced Exercise: Create a dynamic navigation system where the current page is highlighted automatically
- Challenge: Build a complete blog template system with different layouts for the homepage, article pages, and admin pages
By mastering template inheritance in Echo, you'll be able to build more maintainable and consistent web applications with cleaner, more organized template code.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)