1

I'm in the process of learning Spring Webflux.

I've created a little project which render a flux in a Thymeleaf template. This works well, but I would also like to display kind of a loader in the template while the data from the flux are processed.

For this I've initialized the flag value of the loader to true outside of my flux and I switch the value to false on the doOnComplete method of this flux.

The problem is that my flag value never change in the template (but it does on the controller/router).

There is obviously something I'm missing. My guess is that the template have no way to know that my flag changed, but I was not able to understand how to do this.

How to trigger this change in the template ? Is it possible to mix a reactive value (flux) and a non-reactive value in the same Thymeleaf template ?

So far here is my code.

The router/handler

@Configuration
public class PersonRouter {
    @Bean
    public RouterFunction<ServerResponse> personRoutes(PersonHandler personHandler) {
        return route(GET("/"), personHandler::renderPersonsAge);
    }
}
@Component
@RequiredArgsConstructor
public class PersonHandler {
    public static final String API_AGIFY_URI = "https://api.agify.io/";
    private final PersonRepository personRepository;

    public Mono<ServerResponse> renderPersonsAge(ServerRequest ignoredServerRequest) {
        var modelMap = new ModelMap();
        var isLoading = new AtomicBoolean(true);
        var ages = this.fetchPersonAge()
                .doOnComplete(() -> isLoading.set(false));
        var contextVariable = new ReactiveDataDriverContextVariable(ages, 1);
        modelMap.addAttribute("isLoading", isLoading.get());
        modelMap.addAttribute("ages", contextVariable);
        return ServerResponse.ok()
                .contentType(MediaType.TEXT_HTML)
                .render("index", modelMap);
    }

    private Flux<Age> fetchPersonsAge() {
        return this.personRepository
                .findAll()
                .concatMap(this::guessPersonAge)
                .delayElements(Duration.ofNanos(700));
    }

    private Mono<Age> guessPersonAge(Person person) {
        return WebClient.create().get()
                .uri(API_AGIFY_URI + "?name={name}", person.getName())
                .accept(MediaType.APPLICATION_JSON)
                .retrieve()
                .bodyToMono(Age.class);
    }
}

The template:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
    <title>Spring Reactive</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" type="text/css" th:href="@{/main.css}"/>
    <link rel="stylesheet" type="text/css" th:href="@{/webjars/bootstrap/5.2.3/css/bootstrap.css}"/>
</head>
<body>
<main>
    <div class="card">
        <h5 class="card-header">Average age of a person according to his/her name</h5>
        <div class="card-body card__center">
            <table class="table table-striped">
                <thead>
                <tr>
                    <th>Name</th>
                    <th>Count</th>
                    <th>Age</th>
                </tr>
                </thead>
                <tbody>
                <tr data-th-each="age: ${ages}">
                    <td th:text="${age.name}"></td>
                    <td th:text="${age.count}"></td>
                    <td th:text="|${age.age} years|"></td>
                </tr>
                </tbody>
            </table>
            <div th:if="${isLoading}" class="spinner-border" role="status"></div>
        </div>
    </div>
</main>
</body>
</html>

Thanks for any kind of help

Gilles
  • 36
  • 2
  • 8
  • Please trim your code to make it easier to find your problem. Follow these guidelines to create a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example). – Community Mar 18 '23 at 04:46

0 Answers0