tschuehly / spring-view-component

Server-side UI components with spring boot
MIT License
174 stars 8 forks source link

Add component to existing Thymeleaf page #7

Closed linus1412 closed 8 months ago

linus1412 commented 1 year ago

More of a question...

Is it possible to create a @ViewComponent and use it in an existing Thymeleaf page without referencing the ViewComponent in the controller?

For example, if I wanted to create a ViewComponent for a page header that I could drop in to any pre existing page?

Something like this...

PageHeaderViewComponent.java

@ViewComponent
public class PageHeaderViewComponent {
    public ViewContext render(String title, String subtitle) throws Exception {
        if (title == null) {
            throw new Exception("You need to pass in a title");
        }
        return new ViewContext(
                ViewProperty.of("title", title),
                ViewProperty.of("subtitle", subtitle)
        );
    }
}

PageHeaderViewComponent.html

<h1>${pageHeader}</h1>
<h2>${subtitle}</h2>

InfoController.java

@Controller
public class InfoController {

    @GetRequest("/info")
    public String info() {

       // Just return a normal Thymeleaf template, no mention of the ViewComponent

       return "info";
    }
}

info.html

<!DOCTYPE>
<html>
  <body>
    <div view:component name="PageHeaderViewComponent" title="Info" subtitle="All about the app"></div>
    <p>Some more text from the page</p>
  </body>
</html>

I can't see how I could do this from any of the examples. I see you referenced the Ruby on Rails gem, I know that this is possible there.

I'm currently migrating a Rails app to Spring Boot so it would be great to follow the same patterns.

tschuehly commented 1 year ago

Hello @linus1412, in the current version this is not possible. It was previously possible because I evaluated the Spring Beans by name at runtime. I removed this possibility as you couldn't do nested layouts, and you didn't get any compile time type checks: https://github.com/tschuehly/spring-view-component/commit/27b84bd89f8a717725bbc7676bb031499d983f81?diff=unified#diff-11f68cdc15cccdab83887529d3c4b419d3c6c9897d1804df41744d14f791e37d

I think this is a tradeoff, autowiring the viewcomponents makes it more expressive and safer. But I'm open for suggestions. What do you think is the benefit of not having the dependency explicitly?

I just tried it out you could use a @ModelAttribute to reduce duplication: https://github.com/tschuehly/spring-view-component/commit/7f00300e48778921e89ee8223e0b4feefb7327a6?diff=split carbon (8)

linus1412 commented 1 year ago

Hi @tschuehly

Thank you for your response and investigating.

I think the advantage of not having a dependency on setup the controller is that it makes the templates simpler and more isolated. Otherwise the V in the MVC is tightly bound to a specific C. If I had a view component used in many templates and I wanted to change it I would have to change all the templates (expected) and all the controllers for any page that could potentially render that view component.

I think it would be nice if the ViewComponent's just returned String (the html). This would mean that the ViewComponent can still depend on any other injected spring bean, be called from any view without dependency on any specific controller or indeed be inside a controller to return a partial response (say for HTMX)

I did a brain dump of what it could look like (from a usage point of view, not working implementation). It's on another machine but I could post it here of you'd like.

It's a bit more like the Rails version where it just sends params to ViewComponent's and would enable slots.

But thank you, your project has inspired me to have a good think about the web layer in Spring.

Martin

linus1412 commented 1 year ago

BTW I love how the ViewComponent is a spring bean - that opens up loads of possibilities

tschuehly commented 1 year ago

@linus1412 Hey Martin, I think I kinda understand a point. Some thoughts I had: The ViewComponents are just a thin layer above the normal MVC Spring Implementation that give you better structure, reduce cognitive load and (soon™) enable typesafe, compile time checked properties for template languages that don't support it. It was a conscious decision to move the template rendering as much as possible out of the library itself.

Gladly share your brain dump, I can probably gain some insights out of it.

What I don't fully get why a ViewComponent View is tightly bound to a certain Controller? One main point of the library is to remove the tight coupling you have between the Controller and the Model/View, because it is just a Spring Bean and they are dependency injected you can use Inversion of Control and with parameterized ViewComponent and Layout Components you can do exactly what you want. https://github.com/tschuehly/spring-view-component#layout-components