Template Iteration
Loop through collections and arrays to generate dynamic, repeating content in your PDFs.
Learning Objectives
By the end of this article, you’ll be able to:
- Use the `` helper to iterate over collections
- Access current item properties in loops
- Use
andfor position tracking - Create nested loops for complex data
- Handle empty collections gracefully
- Build dynamic tables, lists, and repeating sections
The Helper
The `` helper iterates over arrays and collections:
{{#each collection}}
<!-- Content repeated for each item -->
{{this.property}}
{{/each}}
Basic Iteration
Simple Array
C# Code:
doc.Params["items"] = new[] { "Apple", "Banana", "Orange" };
Template:
<ul>
{{#each items}}
<li>{{this}}</li>
{{/each}}
</ul>
Output:
<ul>
<li>Apple</li>
<li>Banana</li>
<li>Orange</li>
</ul>
Array of Objects
C# Code:
doc.Params["products"] = new[]
{
new { name = "Widget A", price = 10.00 },
new { name = "Widget B", price = 15.00 },
new { name = "Widget C", price = 20.00 }
};
Template:
<ul>
{{#each products}}
<li>{{this.name}} - ${{this.price}}</li>
{{/each}}
</ul>
Output:
<ul>
<li>Widget A - $10.00</li>
<li>Widget B - $15.00</li>
<li>Widget C - $20.00</li>
</ul>
Using
Access the current iteration index (0-based):
<table>
<thead>
<tr>
<th>#</th>
<th>Product</th>
<th>Price</th>
</tr>
</thead>
<tbody>
{{#each products}}
<tr>
<td>{{add(@index, 1)}}</td> <!-- Convert to 1-based -->
<td>{{this.name}}</td>
<td>${{this.price}}</td>
</tr>
{{/each}}
</tbody>
</table>
Using
When iterating over dictionaries or objects with named properties:
C# Code:
doc.Params["stats"] = new Dictionary<string, int>
{
{ "Users", 1250 },
{ "Orders", 3480 },
{ "Revenue", 125000 }
};
Template:
<dl>
{{#each stats}}
<dt>{{@key}}</dt>
<dd>{{this}}</dd>
{{/each}}
</dl>
Output:
<dl>
<dt>Users</dt>
<dd>1250</dd>
<dt>Orders</dt>
<dd>3480</dd>
<dt>Revenue</dt>
<dd>125000</dd>
</dl>
Nested Loops
Two-Level Nesting
C# Code:
doc.Params["departments"] = new[]
{
new
{
name = "Sales",
employees = new[]
{
new { name = "John Doe", title = "Sales Manager" },
new { name = "Jane Smith", title = "Sales Rep" }
}
},
new
{
name = "Engineering",
employees = new[]
{
new { name = "Bob Johnson", title = "Lead Developer" },
new { name = "Alice Brown", title = "Developer" }
}
}
};
Template:
{{#each departments}}
<h2>{{this.name}}</h2>
<ul>
{{#each this.employees}}
<li><strong>{{this.name}}</strong> - {{this.title}}</li>
{{/each}}
</ul>
{{/each}}
Three-Level Nesting
{{#each companies}}
<h1>{{this.companyName}}</h1>
{{#each this.departments}}
<h2>{{this.deptName}}</h2>
<ul>
{{#each this.employees}}
<li>{{this.name}} ({{this.role}})</li>
{{/each}}
</ul>
{{/each}}
{{/each}}
Context Access in Loops
Parent Context
Access parent scope data inside loops:
{{#each orders}}
<p>Order {{this.orderNumber}} for customer {{../customerName}}</p>
{{#each this.items}}
<p> Item: {{this.name}} - Qty: {{this.quantity}}</p>
{{/each}}
{{/each}}
The ../ syntax accesses the parent context.
Root Context
Access the root data from any nested level:
{{#each departments}}
{{#each this.employees}}
<p>{{this.name}} works for {{../../companyName}}</p>
{{/each}}
{{/each}}
Empty Collections
Using
Handle empty collections gracefully:
{{#each products}}
<div class="product">
<h3>{{this.name}}</h3>
<p>Price: ${{this.price}}</p>
</div>
{{else}}
<p>No products available.</p>
{{/each}}
Dynamic Tables
Invoice Line Items
C# Code:
doc.Params["model"] = new
{
invoiceNumber = "INV-2025-001",
items = new[]
{
new { description = "Widget A", quantity = 5, price = 10.00, total = 50.00 },
new { description = "Widget B", quantity = 3, price = 15.00, total = 45.00 },
new { description = "Widget C", quantity = 2, price = 20.00, total = 40.00 }
},
subtotal = 135.00,
tax = 10.80,
total = 145.80
};
Template:
<!DOCTYPE html>
<html xmlns='http://www.w3.org/1999/xhtml'>
<head>
<title>Invoice</title>
<style>
table {
width: 100%;
border-collapse: collapse;
}
th {
background-color: #2563eb;
color: white;
padding: 10pt;
text-align: left;
}
td {
padding: 8pt;
border-bottom: 1pt solid #e5e7eb;
}
.total-row {
background-color: #eff6ff;
font-weight: bold;
}
</style>
</head>
<body>
<h1>Invoice #{{model.invoiceNumber}}</h1>
<table>
<thead>
<tr>
<th>#</th>
<th>Description</th>
<th>Qty</th>
<th>Price</th>
<th style="text-align: right;">Total</th>
</tr>
</thead>
<tbody>
{{#each model.items}}
<tr>
<td>{{add(@index, 1)}}</td>
<td>{{this.description}}</td>
<td>{{this.quantity}}</td>
<td>${{format(this.price, 'F2')}}</td>
<td style="text-align: right;">${{format(this.total, 'F2')}}</td>
</tr>
{{/each}}
</tbody>
<tfoot>
<tr>
<td colspan="4">Subtotal</td>
<td style="text-align: right;">${{format(model.subtotal, 'F2')}}</td>
</tr>
<tr>
<td colspan="4">Tax (8%)</td>
<td style="text-align: right;">${{format(model.tax, 'F2')}}</td>
</tr>
<tr class="total-row">
<td colspan="4">TOTAL</td>
<td style="text-align: right;">${{format(model.total, 'F2')}}</td>
</tr>
</tfoot>
</table>
</body>
</html>
Dynamic Lists
Product Catalog
C# Code:
doc.Params["catalog"] = new
{
categoryName = "Electronics",
products = new[]
{
new
{
name = "Laptop",
price = 999.99,
features = new[] { "16GB RAM", "512GB SSD", "Full HD Display" }
},
new
{
name = "Smartphone",
price = 699.99,
features = new[] { "5G Enabled", "128GB Storage", "Triple Camera" }
}
}
};
Template:
<h1>{{catalog.categoryName}}</h1>
{{#each catalog.products}}
<div class="product">
<h2>{{this.name}}</h2>
<p class="price">${{format(this.price, 'F2')}}</p>
<h3>Features:</h3>
<ul>
{{#each this.features}}
<li>{{this}}</li>
{{/each}}
</ul>
</div>
{{/each}}
Conditional Iteration
Filtering with
<h2>Premium Products</h2>
{{#each products}}
{{#if this.isPremium}}
<div class="premium-product">
<h3>{{this.name}}</h3>
<p>Price: ${{this.price}}</p>
</div>
{{/if}}
{{/each}}
Alternating Styles
{{#each items}}
<div class="{{if(calc(@index, '%', 2), 'odd-row', 'even-row')}}">
{{this.name}}
</div>
{{/each}}
Practical Examples
Example 1: Employee Directory
C# Code:
doc.Params["model"] = new
{
companyName = "Tech Corp",
employees = new[]
{
new { name = "Alice Johnson", department = "Engineering", email = "alice@techcorp.com" },
new { name = "Bob Smith", department = "Sales", email = "bob@techcorp.com" },
new { name = "Carol Williams", department = "Engineering", email = "carol@techcorp.com" },
new { name = "David Brown", department = "HR", email = "david@techcorp.com" }
}
};
Template:
<!DOCTYPE html>
<html xmlns='http://www.w3.org/1999/xhtml'>
<head>
<title>Employee Directory</title>
<style>
body {
font-family: Helvetica, sans-serif;
margin: 40pt;
}
h1 {
color: #1e40af;
border-bottom: 2pt solid #1e40af;
padding-bottom: 10pt;
}
.employee {
padding: 15pt;
margin-bottom: 15pt;
border: 1pt solid #d1d5db;
border-radius: 5pt;
}
.employee:nth-child(even) {
background-color: #f9fafb;
}
.name {
font-size: 14pt;
font-weight: bold;
color: #2563eb;
}
.department {
color: #666;
font-style: italic;
}
</style>
</head>
<body>
<h1>{{model.companyName}} - Employee Directory</h1>
{{#each model.employees}}
<div class="employee">
<div class="name">{{add(@index, 1)}}. {{this.name}}</div>
<div class="department">{{this.department}}</div>
<div class="email">{{this.email}}</div>
</div>
{{/each}}
<p style="margin-top: 30pt; color: #666; font-size: 10pt;">
Total Employees: {{model.employees.length}}
</p>
</body>
</html>
Example 2: Sales Report with Nested Data
C# Code:
doc.Params["model"] = new
{
reportTitle = "Q4 2024 Sales Report",
generatedDate = DateTime.Now,
regions = new[]
{
new
{
regionName = "North America",
totalSales = 1250000,
salespeople = new[]
{
new { name = "John Doe", sales = 450000 },
new { name = "Jane Smith", sales = 400000 },
new { name = "Bob Wilson", sales = 400000 }
}
},
new
{
regionName = "Europe",
totalSales = 980000,
salespeople = new[]
{
new { name = "Alice Brown", sales = 520000 },
new { name = "Charlie Davis", sales = 460000 }
}
}
}
};
Template:
<!DOCTYPE html>
<html xmlns='http://www.w3.org/1999/xhtml'>
<head>
<title>Sales Report</title>
<style>
body {
font-family: Helvetica, sans-serif;
margin: 40pt;
}
h1 {
color: #1e40af;
}
.header-info {
font-size: 10pt;
color: #666;
margin-bottom: 30pt;
}
.region {
margin-bottom: 40pt;
page-break-inside: avoid;
}
h2 {
background-color: #2563eb;
color: white;
padding: 10pt;
margin-bottom: 15pt;
}
table {
width: 100%;
border-collapse: collapse;
margin-bottom: 10pt;
}
th {
background-color: #eff6ff;
padding: 8pt;
text-align: left;
border-bottom: 2pt solid #2563eb;
}
td {
padding: 8pt;
border-bottom: 1pt solid #e5e7eb;
}
.total {
font-weight: bold;
font-size: 14pt;
color: #059669;
}
</style>
</head>
<body>
<h1>{{model.reportTitle}}</h1>
<div class="header-info">
Generated: {{format(model.generatedDate, 'MMMM dd, yyyy HH:mm')}}
</div>
{{#each model.regions}}
<div class="region">
<h2>{{this.regionName}}</h2>
<table>
<thead>
<tr>
<th>Salesperson</th>
<th style="text-align: right;">Sales</th>
<th style="text-align: right;">% of Region</th>
</tr>
</thead>
<tbody>
{{#each this.salespeople}}
<tr>
<td>{{this.name}}</td>
<td style="text-align: right;">{{format(this.sales, 'C0')}}</td>
<td style="text-align: right;">
{{format(calc(this.sales, '/', ../totalSales), 'P0')}}
</td>
</tr>
{{/each}}
</tbody>
</table>
<div class="total">
Region Total: {{format(this.totalSales, 'C0')}}
</div>
</div>
{{/each}}
</body>
</html>
Example 3: Product Grid Layout
<div style="display: table; width: 100%;">
{{#each products}}
<div style="display: table-cell; width: 33%; padding: 10pt; vertical-align: top;">
<div style="border: 1pt solid #d1d5db; padding: 15pt;">
<img src="{{this.imageUrl}}" style="width: 100%; height: auto;" />
<h3>{{this.name}}</h3>
<p class="price">${{format(this.price, 'F2')}}</p>
<p>{{this.description}}</p>
</div>
</div>
{{#if(calc(add(@index, 1), '%', 3))}}
<!-- Continue on same row -->
{{else}}
</div><div style="display: table; width: 100%;">
{{/if}}
{{/each}}
</div>
Try It Yourself
Exercise 1: Student Roster
Create a template that displays:
- Course name and instructor
- List of students with:
- Student number (1, 2, 3…)
- Name
- Grade
- Total student count
Exercise 2: Multi-Level Menu
Create a restaurant menu with:
- Multiple categories (Appetizers, Entrees, Desserts)
- Each category has multiple items
- Each item has name, description, and price
- Add alternating background colors
Exercise 3: Nested Organization Chart
Create an org chart showing:
- Company name
- Multiple departments
- Each department has employees
- Each employee has name, title, and email
- Calculate and show total employees per department
Common Pitfalls
❌ Forgetting
{{#each products}}
<p>{{name}}</p> <!-- Won't work -->
{{/each}}
✅ Solution: Use ``
{{#each products}}
<p>{{this.name}}</p>
{{/each}}
❌ Wrong Context in Nested Loops
{{#each departments}}
{{#each employees}}
<p>{{deptName}}</p> <!-- Wrong scope -->
{{/each}}
{{/each}}
✅ Solution: Use parent context
{{#each departments}}
{{#each this.employees}}
<p>{{../deptName}}</p>
{{/each}}
{{/each}}
❌ Not Handling Empty Collections
{{#each items}}
<div>{{this.name}}</div>
{{/each}}
<!-- Shows nothing if empty -->
✅ Solution: Add
{{#each items}}
<div>{{this.name}}</div>
{{else}}
<p>No items found.</p>
{{/each}}
Next Steps
Now that you can iterate through data:
- Conditional Rendering - Show/hide content dynamically
- Variables & Params - Store and reuse values
- Context & Scope - Master data access patterns
Continue learning → Conditional Rendering