Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[question/proposal] Scoped Slots #646

Closed
leemason opened this issue Apr 11, 2019 · 8 comments
Closed

[question/proposal] Scoped Slots #646

leemason opened this issue Apr 11, 2019 · 8 comments

Comments

@leemason
Copy link

Description

Im currently getting my head round lit element coming from a vue background and its all very promising. One thing that i cant settle in my head is scoped slots.

They are a great feature in vue and ive created a crude example which could work.

Asking here first to see if its something worth further investigation and/or a pr.

It appears the slack channel goes to a heroku app so couldn't see a better place to ask.

The idea being you take the content of the slot and evaluate that in the context of the component itself, providing access to the components properties, yet maintaining the flexibility of the slot.

Ive has soo many use cases for this in vue previously.

The demo is far from a pr example, im just trying to show the concept to see if further investigation is warranted.

Live Demo

https://stackblitz.com/edit/pm5bzr

@webfolderio
Copy link

Pease do not copy and add features from vue, react or similar framework.

From the Readme.md

A simple base class for creating fast, lightweight web components with lit-html.

Lit is not a framework, it's a tiny library.

Keep it simple as much as possible.

@guzmanpaniagua
Copy link

Just a little extended information about this, because I think this request is recurrent and with a reason.

You can create webcomponents with lit element without much problems, until you really try to use it in advanced applications, when you try to generate really reusable webcomponents you realize that create a tag that gets a lot of properties and render some fixed stuff is just not enough.

the real way to generate reusable components is to leverage the slots, you generate a component that gets some properties and render some combination of html but you allow other users to use his own templates.

Polymer 1 and 2 has one of the most powerfull, and sadly little know, behaviour, the templatizer, that allows you to create really reusable components.

all the mayor table components in the opensource and in private companies use templatizer to generate a table that can be used in all scenarios, not just because explorer gets out the templates tags from the table, but because is the way of generate real reusable components.

when we go to the original examples of components, most of them where leveraging the content, some thing like
<chat> <list-messages> <message> <text>hello</text> <user>Guzman</user> </message> </list-messages> </chat>
sadly the current webcomponent comunity thinks that the way to go is just
<chat data="Infotmation"></chat>

and thats limit a lot the components reusability, I think it is important to remember that a webcomponent should be able to adapt to multiple scenarios.

if you are creating a webcomponent and use it only once you are doing something wrong, and also if every time some one has to use your component need to do a pull request or make changes the you are doing also something wrong.

@daKmoR
Copy link
Contributor

daKmoR commented Apr 16, 2019

@guzmanpaniagua you can offer the following public api which should be even more powerful

const messageTemplate = (message) {
  return html`<foo>${message}</foo>`
}

// usage
render() {
  return html`<my-el .messageTemplate=${messageTemplate}><my-el>`;
}

@leemason
Copy link
Author

leemason commented May 8, 2019

@webfolderio Im not trying to suggest lit element gets turned into a framework, providing a feature that "happens" to be invaluable in some popular frameworks i don't think is enough reason to discount it as a valid proposal. I understand the concept of not generating new features everywhere to keep the library focussed, but enhancing an existing feature im struggling to understand the negatives.

Slots are crucial to web components, and it would be wrong to assume this idea wasnt at least in part inspired by existing solutions to templating (ie frameworks).

Slots and especially scoped slots provide tremendous flexibility and enhance the ability to use component composition to structure your markup.

Composition at the html level is much easier to reason about.

Its still a hard goal to achieve, but providing better interfaces to do so enabled more rapid development, happier development experience and guides the community to composition over inheritance.

Take the following example, a component that loads a list of "things" from an api:

<my-thing-list></my-thing-list>

This may render in its shadow root:

<ul>
    <li>thing 1: <a href="urltothing1">view thing 1</a></li>
    <li>thing 2: <a href="urltothing2">view thing 2</a></li>
</ul>

What happens when you want to change the markup of the list items in a separate use of the component?

  1. You extend the components api, making it more complex: <my-thing-list list-type="2"></my-thing-list> keeping the markup private to your implementation, preventing user land customisation
  2. You extend the my-thing-list and create a my-other-thing-list, extending the initial element and overriding its render function.
  3. You do something similar to @daKmoR example

With all of these solutions, you are either making your components more complex, not reusing components, or hiding your markup in javascript and/or away from users.

With a scoped slot you could do the following:

<my-thing-list>
    <span slot="item"><a href="${thing.url}">view ${thing.title}</a></span>
</my-thing-list>

And in another location:

<my-thing-list>
    <span slot="item"><a href="${thing.url}"><h3>view ${thing.title}</h3></a></span>
</my-thing-list>

Im rambling now, there is most likely better examples to illustrate my point but i hope this could be discussed further and some feedback from the maintainers so it's clear not just for me but others in future.

@tsavo-vdb-knott
Copy link

@daKmoR your proposal is exactly what I do currently in a 💯 LitElement app with 200+ comps. It works incredibly well, I would even go as far as saying it is more powerful and dynamic than slots from querying to styling to dynamic layouts/polymorphism and more. Just thought I would throw in my two cents.

@leemason
Copy link
Author

leemason commented May 8, 2019

Just to clarify im not suggesting @daKmoR solution isnt valid, it is as it stands the correct solution to this problem, my point is that solution enforces the end user to create a new element to use an element.

Think from a component library perspective, you're enforcing the user to write their markup in javascript, making distribution more of a burden on the component user.

Say a CMS user wants to use a <g-maps></g-maps> component, but change the icon used for locations, the current solution requires you to create a wrapping component:

const pointTemplate = (locationName) {
  return html`<strong>${locationName}</strong>`
}

// usage
render() {
  return html`<g-maps .pointTemplate=${pointTemplate}><g-maps>`;
}

With a scoped slot you would simply import the component in your document, and use like this:

<g-maps>
    <strong slot="pointer">${locationName}</strong>
</g-maps>

A full javascript oriented solution like that suggested by @daKmoR is and always will be more flexible, but it puts the burden on end users to understand lit element, compose via javascript and not html markup, which less experienced/confident end users will shy away from.

@SampsonCrowley
Copy link

SampsonCrowley commented Jul 16, 2019

@leemason you can use the assignedSlot property to get that info. I don't like the syntax of scoped slots. there's too much unnecessary magic going on there. @webfolderio it would be nice if passing props to the slot were just available to the slotted element instead of needing to use the assignedSlot property though

@justinfagnani
Copy link
Contributor

We definitely don't want to start evaluating JavaScript expressions from HTML. That's a huge XSS hole, and a non-starter for this project.

There are use cases for passing templates to components to be evaluated by the component. @daKmoR showed the simplest and most powerful solution, which is equivalent to render props in the React ecosystem. The only downside there is that this API is not available from plain HTML.

In order to allow templated content from HTML, we really need an HTML-based templating system, and the component's contract needs to specify that system. It's necessarily a tight coupling (until we get Template Instantiation in the browser, at least). This tight coupling is potentially a problem, and one that's not apparent in a framework because everything in the framework is a tight coupling. It doesn't stand out as a problem that Vue scoped slots use Vue template syntax because everything in Vue does. With web components, you'd have to document that your component uses lit-html syntax, while your users should ideally not know or care that the component was even implemented with LitElement.

One way forward here would be for lit-html to add an HTML-based template system. There's an issue for that there already.

WIth that, components could allow for template children, like:

<my-element>
  <template>
    <strong slot="pointer">{{locationName}}</strong>
  </template>
</my-element>

This is actually possible today if you bring together the right pieces. For an example see Microsoft's graph toolkit components, which use LitElement: https://github.com/microsoftgraph/microsoft-graph-toolkit/blob/master/docs/templates.md

I think we can close this issue for now, as there won't be any work here until we have better options for templating from within HTML itself. In the meantime I recommend the render props pattern.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants