Search code examples
spring-bootthymeleaf

Spring Boot Thymeleaf - How to use same data in two different fragments?


I have a Spring Boot 3 application with Thymeleaf in the front end. The page looks like this:

<!DOCTYPE html>

<html xmlns:th="http://www.thymeleaf.org" xmlns="http://www.w3.org/1999/html" th:lang="en" lang="en">

<!-- Head-->
<head th:replace="~{fragments/head :: head}"></head>

<body>

  <div th:if="${profileSection == 'Profile info'}">
      <div th:replace="~{fragments/user/profile-info :: profileInfo}"></div>
  </div>

  <div th:if="${profileSection == 'Downloads'}">
      <div th:replace="~{fragments/user/downloads :: downloads}"></div>
  </div>

</body>

</html>

When a user clicks on the profile link, the above page will be rendered with profileSection == 'Profile info' so that fragments/user/profile-info :: profileInfo will be rendered first and the user details like name, email, phone nr, etc will be rendered. There is a sidebar with let's say 2 links. Profile info & Downloads.

What I want is this: When a user clicks on the Download link from the sidebar, fragments/user/downloads :: downloads should be rendered in the middle of the page without fetching downloads from the backend, instead it should use the current user object like: user.getDownloads() to render the download list. In that way, I don't need to do an extra round to the DB to fetch the user again. The user has a @ManyToMany relationship with the Download entity.

How can I do this? Or, is it even possible?


Solution

  • If a User's downloads contain a lot of data, it can consume a lot of memory unnecessarily, as the user may not access the downloads section at all. There is a better approach to this problem.

    To start, make sure the @ManyToMany relation has FetchType.LAZY (which is the default setting, so you should be fine if you haven't specified FetchType.EAGER explicitly). This way, the memory won't be filled up unless the user specifically requests the downloads.

    When the user clicks on the Downloads link in the sidebar, you should have a code like this in the HTML file:

    <a th:href="@{/user/downloads}">Downloads</a>
    

    In the Controller class, you can have something like:

    @GetMapping("/user/downloads")
    public String getDownloads(@AuthenticationPrincipal(expression = "user") User user, Model model) {
        user.getDownloads().size(); // Now you have access to downloads
        model.addAttribute("profileSection", "Downloads");
        model.addAttribute("user", user);
        return "user/profile-page";
    }
    

    Make sure to import the AuthenticationPrincipal annotation:

    org.springframework.security.core.annotation.AuthenticationPrincipal
    

    Do not use this one, it's deprecated:

    org.springframework.security.web.bind.annotation.AuthenticationPrincipal
    

    Spring Boot provides the logged-in user in your Controller method, so just add the user object to the model, and inside the fragments/user/downloads :: downloads fragment, you can do something like below snippet to loop through the user's download list:

    <div th:each="download : ${user.downloads}">
       ...
    </div>
    

    Note: Be careful if you are using Lombok. The @Data / @Getter annotations can cause lazy items to load without an explicit call.