Thymeleafのeachの基本!繰り返し処理の書き方
新人
「HTMLにJavaのデータを表示したいんですが、ループ処理ってどうやって書けばいいんですか?」
先輩
「それなら、Thymeleafのeach属性を使えば、リストなどを繰り返し表示できますよ。」
新人
「Thymeleafってそもそも何なんですか?Javaとどう関係があるんですか?」
先輩
「いい質問ですね。まずはThymeleafがどんなテンプレートエンジンなのかを理解しておきましょう。」
1. Thymeleafとは?
Thymeleaf(タイムリーフ)は、Javaアプリケーションと連携してHTMLテンプレートを動的に生成できるテンプレートエンジンです。Spring Frameworkと非常に相性が良く、Spring Bootでもよく使われています。Thymeleafを使うことで、サーバーサイドのJavaオブジェクトをHTMLに自然な形で埋め込むことができ、動的なWebページを効率よく作成できます。
HTMLファイルに直接Thymeleafの属性(th:textやth:eachなど)を追加することで、画面にJavaの値を表示したり、繰り返し表示、条件分岐などが可能になります。
2. HTMLに組み込めるThymeleafの特徴
ThymeleafはHTMLテンプレートとしてそのままブラウザで表示できる「ナチュラルテンプレート」機能が特徴です。つまり、Thymeleafの構文を埋め込んだHTMLファイルを、開発中でも通常のHTMLとしてブラウザで確認できるため、デザイナーとエンジニアが同じファイルを扱いやすいというメリットがあります。
また、Spring MVCの@ModelAttributeやModelに追加したJavaオブジェクトを簡単にHTMLテンプレートに渡して表示できます。これにより、コントローラでデータを用意して画面に表示するまでの流れが非常にスムーズになります。
3. each属性とは何か?基本的な使い方と意味
Thymeleafで繰り返し処理を行いたいときに使うのがth:each属性です。Javaで用意したリストや配列の要素を、HTML上で繰り返し表示できます。たとえば、名前の一覧をループで表示したいときには、以下のように書きます。
<ul>
<li th:each="name : ${nameList}" th:text="${name}"></li>
</ul>
このコードでは、コントローラから渡されたnameList(たとえばList<String>型の名前リスト)を、th:eachで1件ずつ取り出して
このname : ${nameList}という記述は、拡張for文のような構文です。nameには1つずつの要素が入り、${name}で表示されます。
コントローラ側のJavaコードは以下のように記述します。
@Controller
public class SampleController {
@GetMapping("/names")
public String showNames(Model model) {
List<String> nameList = List.of("佐藤", "鈴木", "高橋", "田中");
model.addAttribute("nameList", nameList);
return "names";
}
}
このようにして、Springの@ControllerでModelにリストを追加し、Thymeleafでそのリストをth:eachで表示します。
繰り返し処理を使うことで、例えば商品一覧、ユーザー一覧、コメント一覧など、さまざまな画面で役立つ実装が簡単にできるようになります。
4. eachの基本構文(th:each)の使い方と記述例
Thymeleafのth:eachは、HTMLの要素に対して繰り返し処理を実装するための属性です。基本構文は次のようになります。
<tr th:each="item : ${itemList}">
<td th:text="${item}"></td>
</tr>
この構文では、Javaで作成したitemListの中身を1つずつ取り出してitemに代入し、それを表示しています。ループ処理を行うHTML要素に対してth:eachを指定するだけで、複数のデータを動的にレンダリングできます。
例えば、次のように商品名の一覧を表示するテーブルを作ることが可能です。
<table>
<thead>
<tr>
<th>商品名</th>
</tr>
</thead>
<tbody>
<tr th:each="product : ${productList}">
<td th:text="${product}"></td>
</tr>
</tbody>
</table>
このようにth:eachを活用することで、データベースなどから取得した複数データを自動的にHTMLへ展開できます。Thymeleafテンプレートで繰り返し処理を取り入れる際には、まずこの構文を覚えておくと良いでしょう。
5. 繰り返し中の変数(status変数など)の使い方
Thymeleafのth:eachでは、ループの現在の状態を表すステータス変数を使うこともできます。構文は次のように書きます。
<tr th:each="item, stat : ${itemList}">
<td th:text="${stat.index} + 1"></td>
<td th:text="${item}"></td>
</tr>
この例ではstatがステータス変数です。stat.indexは0から始まるインデックス、stat.countは1から始まるカウント番号を表します。
この機能を使うことで、ループの中で何番目かを表示したり、偶数行と奇数行でスタイルを切り替えるといった応用が可能です。たとえば以下のように記述します。
<tr th:each="user, stat : ${userList}"
th:class="${stat.even}? 'even-row' : 'odd-row'">
<td th:text="${stat.count}"></td>
<td th:text="${user.name}"></td>
</tr>
このコードでは、stat.evenを使って偶数行にはeven-rowというクラスを、奇数行にはodd-rowというクラスを動的に付与しています。
このように、th:eachのステータス変数を使うことで、より柔軟なテンプレート処理が実現できます。
6. Java側(@Controller)でListデータを用意し、Thymeleafで表示する流れ
Thymeleafのth:eachを使って繰り返し処理を行うには、まずJava側で表示したいデータをList形式などで用意し、それをSpringの@Controllerクラスから画面に渡す必要があります。
以下は、Gradleプロジェクトでpleiades上に作成したSpring Bootアプリケーションのサンプルです。
@Controller
public class ProductController {
@GetMapping("/products")
public String showProducts(Model model) {
List<String> productList = List.of("りんご", "バナナ", "みかん", "ぶどう");
model.addAttribute("productList", productList);
return "product-list";
}
}
このコントローラでは、/productsというURLにアクセスしたときに、文字列のリストproductListをモデルに追加しています。
続いて、Thymeleafテンプレート(resources/templates/product-list.html)で以下のように繰り返し表示を行います。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>商品一覧</title>
</head>
<body>
<h1>商品一覧</h1>
<ul>
<li th:each="item, stat : ${productList}">
<span th:text="'No.' + ${stat.count} + ' - ' + ${item}"></span>
</li>
</ul>
</body>
</html>
このHTMLテンプレートでは、th:eachでproductListの要素を1件ずつ取り出し、それぞれのアイテムに番号を付けて表示しています。
このように、Javaで用意したデータをModelに追加し、Thymeleafテンプレートでth:eachを使って繰り返し処理することで、リスト形式のデータを簡単に画面に表示できます。
Spring MVCとThymeleafの連携は非常にスムーズであり、初心者でも迷わず使える構成になっています。Gradleベースのプロジェクトでも、pleiades環境で簡単に動作確認ができるため、開発環境としても扱いやすいのが特徴です。
7. eachのネスト(入れ子)で繰り返す方法
Thymeleafではth:eachを入れ子にして、リストの中のリストを表示するようなネスト構造の繰り返し処理が可能です。たとえば、カテゴリごとに商品を分類して表示したい場合、カテゴリのリストの中に商品リストを含むような構造を使います。
まずはJava側のデータ構造を定義します。
public class Category {
private String name;
private List<String> items;
public Category(String name, List<String> items) {
this.name = name;
this.items = items;
}
public String getName() { return name; }
public List<String> getItems() { return items; }
}
そして、コントローラでは以下のようにカテゴリリストを用意してModelに追加します。
@Controller
public class CategoryController {
@GetMapping("/categories")
public String showCategories(Model model) {
List<Category> categoryList = List.of(
new Category("フルーツ", List.of("りんご", "バナナ", "みかん")),
new Category("野菜", List.of("にんじん", "キャベツ", "だいこん"))
);
model.addAttribute("categoryList", categoryList);
return "category-list";
}
}
続いてThymeleafテンプレートでネストされたth:eachを使って以下のように表示します。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>カテゴリ別商品一覧</title>
</head>
<body>
<h1>カテゴリ別商品一覧</h1>
<div th:each="category : ${categoryList}">
<h2 th:text="${category.name}"></h2>
<ul>
<li th:each="item : ${category.items}" th:text="${item}"></li>
</ul>
</div>
</body>
</html>
このように、categoryListの中にあるitemsリストを繰り返し処理することで、ネストされたデータ構造を画面に表示できます。Thymeleaf each ネストの処理は非常に直感的で、HTMLの構造を崩さずに記述できるのが特徴です。
8. th:ifと組み合わせて条件付きで繰り返す方法
Thymeleafではth:eachとth:ifを組み合わせて、条件を満たすデータだけを繰り返し表示することも可能です。たとえば、空文字や特定のキーワードを除外したい場合に便利です。
以下は「在庫あり」の商品のみを表示する例です。まず、Java側のデータクラスを用意します。
public class Product {
private String name;
private boolean inStock;
public Product(String name, boolean inStock) {
this.name = name;
this.inStock = inStock;
}
public String getName() { return name; }
public boolean isInStock() { return inStock; }
}
コントローラで商品リストを作成し、Modelに追加します。
@Controller
public class StockController {
@GetMapping("/stock")
public String showStock(Model model) {
List<Product> productList = List.of(
new Product("テレビ", true),
new Product("冷蔵庫", false),
new Product("洗濯機", true)
);
model.addAttribute("productList", productList);
return "stock-list";
}
}
HTMLテンプレートではth:ifを使って、inStockがtrueの商品のみを表示します。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>在庫一覧</title>
</head>
<body>
<h1>在庫ありの商品一覧</h1>
<ul>
<li th:each="product : ${productList}" th:if="${product.inStock}">
<span th:text="${product.name}"></span>
</li>
</ul>
</body>
</html>
th:ifによって、繰り返し処理の中でも表示条件を柔軟に制御できます。th:unlessを使えば逆の条件(在庫なしなど)も簡単に扱えます。Thymeleafテンプレートの中でth:eachと条件分岐を組み合わせることで、より現実的な表示ロジックが実現できます。
9. よくあるエラーと対処法(eachでリストがnullの場合など)
Thymeleafのth:eachを使う際に初心者がよくつまずくポイントのひとつが、リストがnullのときのエラーです。モデルにデータが存在しない、もしくはコントローラでaddAttributeを忘れていた場合、テンプレートが正常にレンダリングされず例外が発生します。
たとえば以下のようなケースでエラーになります。
<ul>
<li th:each="item : ${undefinedList}" th:text="${item}"></li>
</ul>
この例では、undefinedListがModelに存在しないため、Thymeleafがnullとして扱い、繰り返し処理で例外が出る可能性があります。
このような問題を避けるためには、以下のようにth:ifを使ってnullチェックを入れると安全です。
<ul th:if="${itemList != null}">
<li th:each="item : ${itemList}" th:text="${item}"></li>
</ul>
または、コントローラ側で空のリストを初期化して渡すようにすれば、テンプレートでのエラーを防げます。
@GetMapping("/safe")
public String showSafe(Model model) {
List<String> itemList = new ArrayList<>(); // 空のリスト
model.addAttribute("itemList", itemList);
return "safe-list";
}
このようにThymeleaf each エラーの回避策として、nullチェックや空のリスト初期化を意識することが大切です。初学者のうちは、nullによるエラーでテンプレートが真っ白になることもあるため、早い段階でエラーの原因を把握し、適切な対処方法を身につけておくと安心です。