Spring ViewComponent allows you to create typesafe, reusable & encapsulated server-rendered UI components.
- What’s a ViewComponent?
- Render a ViewComponent
- Nesting ViewComponents:
- Local Development Configuration
- Production Configuration
- Installation
- Technical Implementation
ViewComponents consolidate the logic needed for a template into a single class, resulting in a cohesive object that is easy to understand. (Source: ViewComponent for Rails)
A Spring ViewComponent is a Spring Bean that defines the context for our Template:
Java
@ViewComponent
public class SimpleViewComponent {
public record SimpleView(String helloWorld) implements ViewContext {
}
public SimpleView render() {
return new SimpleView("Hello World");
}
}
We define the context by creating a record that implements the ViewContext interface
Next, we add the @ViewComponent
annotation to a class and define a method that returns the SimpleView
record.
Kotlin
// SimpleViewComponent.kt
@ViewComponent
class SimpleViewComponent {
fun render() = SimpleView("Hello World")
data class SimpleView(val helloWorld: String) : ViewContext
}
A ViewComponent always needs a corresponding HTML Template. We define the Template in the SimpleViewComponent.[html/jte/kte] In the same package as our ViewComponent class.
We can use Thymeleaf
// SimpleViewComponent.html
<!--/*@thymesVar id="d" type="de.tschuehly.example.thymeleafjava.web.simple.SimpleViewComponent.SimpleView"*/-->
<div th:text="${simpleView.helloWorld()}"></div>
or JTE
// SimpleViewComponent.jte
@param de.tschuehly.example.jte.web.simple.SimpleViewComponent.SimpleView simpleView
<div>${simpleView.helloWorld()}</div>
or KTE
@param simpleView: de.tschuehly.kteviewcomponentexample.web.simple.SimpleViewComponent.SimpleView
<div>
<h2>This is the SimpleViewComponent</h2>
<div>${simpleView.helloWorld}</div>
</div>
We can then call the render method in our controller to render the template.
Java
@Controller
public class SimpleController {
private final SimpleViewComponent simpleViewComponent;
public TestController(SimpleViewComponent simpleViewComponent) {
this.simpleViewComponent = simpleViewComponent;
}
@GetMapping("/")
ViewContext simple() {
return simpleViewComponent.render();
}
}
Kotlin
// Router.kt
@Controller
class SimpleController(
private val simpleViewComponent: SimpleViewComponent,
) {
@GetMapping("/")
fun simpleComponent() = simpleViewComponent.render()
}
If you want to get started right away you can find examples for all possible language combinations here: Examples
We can nest components by passing a ViewContext as property of our record, if we also have it as parameter of our render method we can easily create layouts:
Java
@ViewComponent
public
class LayoutViewComponent {
private record LayoutView(ViewContext nestedViewComponent) implements ViewContext {
}
public ViewContext render(ViewContext nestedViewComponent) {
return new LayoutView(nestedViewComponent);
}
}
Kotlin
@ViewComponent
class LayoutViewComponent {
data class LayoutView(val nestedViewComponent: ViewContext) : ViewContext
fun render(nestedViewComponent: ViewContext) = LayoutView(nestedViewComponent)
}
In Thymeleaf we render the passed ViewComponent with the view:component="${viewContext}"
attribute.
<nav>
This is a navbar
</nav>
<!--/*@thymesVar id="layoutView" type="de.tschuehly.example.thymeleafjava.web.layout.LayoutViewComponent.LayoutView"*/-->
<div view:component="${layoutView.nestedViewComponent()}"></div>
<footer>
This is a footer
</footer>
In JTE/KTE we can just call the LayoutView record directly in an expression:
@param layoutView: de.tschuehly.kteviewcomponentexample.web.layout.LayoutViewComponent.LayoutView
<nav>
This is a Navbar
</nav>
<body>
${layoutView.nestedViewComponent}
</body>
<footer>
This is a footer
</footer>
You can enable hot-reloading of the templates in development, you need to have Spring Boot DevTools as a dependency.
spring.view-component.local-development=true
LATEST_VERSION on Maven Central
Gradle
implementation("de.tschuehly:spring-view-component-thymeleaf:0.8.2")
sourceSets {
main {
resources {
srcDir("src/main/java")
exclude("**/*.java")
}
}
}
Maven
<project>
<dependencies>
<dependency>
<groupId>de.tschuehly</groupId>
<artifactId>spring-view-component-thymeleaf</artifactId>
<version>0.8.2</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.html</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-resources-plugin</artifactId>5
<version>3.3.0</version>
</plugin>
</plugins>
</build>
</project>
LATEST_VERSION on Maven Central
Gradle
plugins {
id("gg.jte.gradle") version("3.1.12")
}
implementation("de.tschuehly:spring-view-component-jte:0.8.2")
jte{
generate()
sourceDirectory = Path("src/main/java")
}
Maven
<project >
<dependencies>
<dependency>
<groupId>de.tschuehly</groupId>
<artifactId>spring-view-component-jte</artifactId>
<version>0.8.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>gg.jte</groupId>
<artifactId>jte-maven-plugin</artifactId>
<version>3.1.12</version>
<configuration>
<sourceDirectory>${project.basedir}/src/main/java</sourceDirectory>
<contentType>Html</contentType>
</configuration>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
LATEST_VERSION on Maven Central
Gradle
implementation("de.tschuehly:spring-view-component-kte:0.8.2")
jte{
generate()
sourceDirectory = Path("src/main/kotlin")
}
tasks.withType<KaptGenerateStubs>(){
dependsOn("generateJte")
}
Spring ViewComponent wraps the Spring MVC container using an AspectJ Aspect and automatically resolves the template and puts the ViewContext in the ModelAndViewContainer