Search code examples
javaspring-bootthymeleaf

Thymeleaf fragment is loaded before method is executed


I have a Controller like this:

@Controller
public class DeviceController {

  @Inject
  private DeviceService deviceService;

  @ModelAttribute("devices")
  public List<Device> getDevices() {
    return deviceService.getAll();
  }

  @GetMapping({"/", "index.html"})
  public String showIndex() {
    return "index";
  }

  @DeleteMapping(value = "/devices/{id}")
  public String deleteOne(@PathVariable("id") long id) {
    deviceService.deleteOne(id);
    return "index :: devices";
  }
}

And a Thymeleaf template like this:

<table id="tbl_device" th:fragment="devices">
  <tr> <!-- header --> </tr>
  <tr th:each="e : ${devices}" th:object="${d}" th:id="'device_' + *{id}" th:fragment="'device_' + *{id}">
    <!-- columns -->
  </tr>
</table>

When I call the /devices/{id} DELETE endpoint, I would expect it to return the table without the deleted device. But it actually returns the table including the deleted device. When I debug the code, I can see that getDevices() is called before deleteOne(id).

When I manually reload the page after deleting, the row is (correctly) not displayed anymore.

Why is that? And (how) can I change this behaviour?
Thanks


Solution

  • Why is that?

    I recommend to read this article. According to that:

    In general, Spring-MVC will always make a call first to that method, before it calls any request handler methods. That is, @ModelAttribute methods are invoked before the controller methods annotated with @RequestMapping are invoked. The logic behind the sequence is that, the model object has to be created before any processing starts inside the controller methods.

    I doubt you can alter invocation order, but what you can do is additionally pass model attribute to your deleteOne method and modify it there.

    @DeleteMapping(value = "/devices/{id}")
    public String deleteOne(@PathVariable("id") long id, @ModelAttribute("devices") List<Device> devices) {
        deviceService.deleteOne(id);
        devices.remove( /* just deleted device */);
        return "index :: devices";
    }