Thymeleaf ViewComponent - A new approach to building SSR web applications with Spring Boot
While developing my side project over the last year with thymeleaf I noticed that the templates you serve with the Controller get quite large. You can split them up by using Thymeleaf fragments. But when you nest multiple fragments and use variable expression it is going to get hard to test and quite fragile.
That’s why the Idea of ViewComponents came along. There is a similar Library already available for Ruby on Rails which are inspired by react.
Kotlin⌗
Demo Repository with Kotlin and Gradle
Creating a ViewComponent⌗
We just need to add the @ViewComponent annotation to a class and define a render() method that returns a ViewContext. We can pass the properties we want to use in our template.
// HomeViewComponent.kt
@ViewComponent
class HomeViewComponent(
private val exampleService: ExampleService
) {
fun render() = ViewContext(
"helloWorld" toProperty "Hello World",
"coffee" toProperty exampleService.getCoffee()
)
}
Next we define the HTML in the HomeViewComponent.html in the same package as our ViewComponent Class.
// HomeViewComponent.html
<html xmlns:th="http://www.thymeleaf.org">
<body>
<div th:text="${helloWorld}"></div>
<br>
<strong th:text="${coffee}"></strong>
</body>
</html>
We can then call the render method in our Controller
// Router.kt
@Controller
class Router(
val homeViewComponent: HomeViewComponent
) {
@GetMapping("/")
fun homeComponent(): Any {
return homeViewComponent.render()
}
}
If we now access the root url path of our spring application we can see that the component renders properly:
Nesting components:⌗
We can also embed components to our templates with the attribute view:component="componentName"
.
<div view:component="navigationViewComponent"></div>
When our render method has parameters we can pass them by using the .render(parameter)
method.
<div view:component="parameterViewComponent.render('Hello World')"></div>
Parameter components:⌗
We can also create components with parameters. We can either use default values when we pass a null value, get a property from a Service or we can throw a custom error.
// ParameterViewComponent.kt
@ViewComponent
class ParameterViewComponent{
fun render(parameter: String?) = ViewContext(
"office" toProperty (parameter ?: throw Error("You need to pass in a parameter")),
)
}
// ParameterViewComponent.html
<h2>ParameterComponent:</h2>
<div th:text="${office}"></div>
This enables us to define the properties for our ParameterViewComponent in the HomeViewComponent:
// HomeViewComponent.kt
@ViewComponent
class HomeViewComponent(
private val exampleService: ExampleService,
) {
fun render() = ViewContext(
"helloWorld" toProperty exampleService.getHelloWorld(),
"office" toProperty exampleService.getOfficeHours()
)
}
// HomeViewComponent.html
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<body>
<div th:text="${helloWorld}"></div>
<div view:component="parameterViewComponent.render(office)"></div>
</body>
</html>
If we now access the root url path of our spring application we can see that the parameter component renders properly:
Gradle Installation⌗
Add this snippet to your build.gradle.kts:
// build.gradle.kts
repositories {
maven("https://jitpack.io")
}
dependencies {
implementation("com.github.tschuehly:thymeleaf-view-component:0.3.0")
}
sourceSets {
main {
resources {
srcDir("src/main/kotlin")
exclude("**/*.kt")
}
}
}