dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.23k stars 9.95k forks source link

Blazor: Expose ICascadingValueComponent or otherwise allow implementing of custom Cascading Value Providers #18743

Open fitdev opened 4 years ago

fitdev commented 4 years ago

Is your feature request related to a problem?

Using CascadingValue may be too verbose if multiple values are needed somewhere down the line (i.e. it involves nesting of CascadingValues). In addition the behavior of CascadingValue is not as flexible.

Describe the solution you'd like

Therefore it would be nice to be able to author custom components that could also act as Cascading Value Providers by implementing ICascadingValueComponent interface or something similar.

Additional context

One scenario where it could be useful are templates: applying a bunch of different parameters / properties to one or more components of a certain type within a given scope (container). For example, there may be a general purpose, highly customizable "button" component, however it may be that within a certain scope (container) only particular buttons need to be rendered, so it would be nice to provide defaults for various desired parameters / properties of the button component which will be automatically applied to all button component instances in the container.

If the container is a custom component itself (quite often the case), then it would be nice to have the ability to specify those parameters / properties - defaults for a particular type of child control (i.e. button in this example) within the markup for the container control itself.

Alternatively, razor markup could be extended with some kind of container-scoped template element that would provide full intellisense for any components and provide defaults for the values of parameters of a given component type within that container scope:

<Container>

//Version 1:
<Template For="ComponentA" Prop1="SomeValue" Prop2="SomeValue" />
//Version 2:
<ComponentA:Template  Prop1="SomeValue" Prop2="SomeValue" />

<ComponentA></ComponentA> //<- Prop1 and Prop2 values from the template will be applied to all instances of ComponentA within the Container unless overriden by inline values

</Container>
mkArtakMSFT commented 4 years ago

Thanks for contacting us. Would it be enough if you could pass multiple values to the CascadingValue?

fitdev commented 4 years ago

@mkArtakMSFT Yes, that may certainly work. However, I think that the notion of templates or something similar is still valuable in its own right. It not only reduces the amount of code one has to write by repeating same values over and over again, but also makes the whole design and intent a lot clearer.

CascadingValue is a good fit when one needs to pass in a few values in an ad-hoc way. But a more systematic approach via templates or something similar is desirable.

mkArtakMSFT commented 4 years ago

Thanks for the details, @fitdev. We are moving this issue to the backlog to see how the community reacts to this ask. We will re-evaluate this ask in the future.

mrpmorris commented 4 years ago

Instead of passing multiple values, why not pass a single object with multiple properties?

https://blazor-university.com/components/cascading-values/cascading-values-by-type/

fitdev commented 4 years ago

@mrpmorris Thanks for the tip. However often the values that need to be passed may be unrelated to each other and it would not make sense to wrap them in a single object. Not to mention the fact that in some advanced scenarios complete control over the way which values are passed may be needed, which is why making ICascadingValueComponent public would be beneficial, so that custom implementations could be made of it.

mrpmorris commented 4 years ago

complete control over the way which values are passed may be needed

What do you mean by this, can you give an example please?

Andras-Csanyi commented 4 years ago

@fitdev IMHO, If you need custom control over how the values are passed through then using a service for managing the values moving between components and managing the way how they passed through (whatever it means) seems more logical. My gut feeling is that testability could be better too, however I haven't done any component testing so far.

mrpmorris commented 4 years ago

Blazor expects cascading values to be in a specific place so it can inject those values into [CascadingParameter] decorated properties.

fitdev commented 4 years ago

@SayusiAndo Thank you for your suggestion. Unfortunately this would not work for me for a number of reasons:

First, Services serve a different purpose - typically providing access to tangential data used by component in some fashion.

Secondly, their extensive and widespread use would be costly at runtime and require a lot of code to write an maintain.

Third, a service would still need to somehow convey that say within ContainerA in our component we pass ObjectA, while in ContainerB a different instance should be passed - ObjectB.

Also, for now my requirements are mainly for static rendering (yes, I know, perhaps an unusual use of Blazor Components) without the need for a state change during runtime - components are rendered only once.

In my typical use case I would basically need to say that all instances of say LinkItemComponent should use such and such back color, icon, alignment (instead of some defaults, and instead of specifying those explicitly a million times for each an every instance). Note that all properties are directly related to the LinkItemComponent, and it would make little sense to create a service to basically supply component property values - something that should be managed by a component directly, let alone to supply a specific combination of such property values.

I therefore think that allowing for extensibility mechanism based on ICascadingValueComponent or perhaps in some other fashion would easily allow one to implement this sample use case, as well as more advanced scenarios in a very simple and user-friendly fashion without much boiler plate code or hacks.

fitdev commented 4 years ago

What do you mean by this, can you give an example please?

@mrpmorris Being able to create own Cascading Value Providers would be especially useful for passing those values dynamically for example, since ICascadingValueComponent.CanSupplyValue can be implemented in various ways to account for different needs, not to mention the ability to control subscriptions as well.

mrpmorris commented 4 years ago

@fitdev Sorry to repeat the question, but I don't think you've answered the exact question I had in mind. I'm interested in reading an example scenario.

Thanks

david-driscoll commented 4 years ago

I think being able to implement ICascadingValueComponent would be a great way to allow for flexibility in components without having to do some crazy nesting of <CascadingValue>. Being to able to surface data from a custom router, layout or other component easily would be extremely powerful.

mrpmorris commented 4 years ago

I too now need this.

I want to write a base component that can be inherited by a page, and I need that base component to set some cascading values. The problem is, there is no way in a page to wrap CascadingValue around @ChildContent, because a pages don't have a @ChildContent they simply override BuildRenderTree instead.

So, without making ICascadingValueComponent public, there is currently no way to create a base page component that sets cascading parameters :(

fitdev commented 4 years ago

@mrpmorris That's exactly the problem I have too. Glad somebody else came across the need for such a scenario!

david-driscoll commented 4 years ago

So the motivation for this is to be able to do something like what vue has with provide / inject

[CascadingParameter] solves the inject portion of things. Only one component can ever provide values and that is CascadingValue. Being able to implement ICascadingValueComponent would allow anyone to provide values for it's siblings.

This opens compelling scenarios where you can have nested components can easily inherit information from their parent. Otherwise you have do something ridiculous like this

Some great scenarios this opens are:

craigajohnson commented 3 years ago

Gigantic upvotes for this capability

david-driscoll commented 3 years ago

I have created a Design Proposal and Prototype for this.

The prototype is just that a prototype, and will likely evolve if the proposal is taken up (hopefully!)

gerneio commented 2 years ago

Does anyone know if anything came out of the design proposal (#30021). Looks like the proposal and prototype (#29971) are currently closed in favor of not adding additional complexity to the framework and rather developers using community-built (or self-built) libraries to solve the unappealing long-chain of CascadingValues. I sure don't want to maintain my own code for it if I don't have to (especially being new to Blazor), so has a community library been built for this yet? I took a quick look at awesome-blazor, but didn't see anything in regards to this specific pain point.

ghost commented 9 months ago

We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.