[Blazor] Dynamic components design proposal #32352
Labels
area-blazor
Includes: Blazor, Razor Components
design-proposal
This issue represents a design proposal for a different issue, linked in the description
Done
This issue has been fixed
Milestone
Summary
We want to enable the capability of interacting with Blazor components from JavaScript as well as support interacting with other javascript frameworks that might be running on the page. To support this, we will enable creating Blazor components from JavaScript and attach those components to specific DOM elements. We will support passing parameters to those components via JavaScript as well as removing those components from the DOM once they are no longer needed.
Motivation
We want to support integrating Blazor with other component frameworks and just be another "JavaScript" framework in general that developers can integrate with their applications. That means developers should be able to have an existing application like an MVC + jquery, or an existing SPA application (like React, Angular, Vue, Svelte, etc.), be capable to integrate Blazor components dynamically whenever they need it and interact with them from within the Browser.
Goals
component libraries
that can offer reusable Blazor components for consumption from other applications.Non-goals
TBD
Scenarios
Risks
Interaction with other parts of the framework
TBD
Detailed design
This is a very large area, so we will divide the design in phases, each of which will provide additional capabilities and deliver incremental value to customers.
Phase one - Enable interaction with Blazor from JavaScript
This phase covers the minimum work strictly required to unblock customers to interact with Blazor from JavaScript, which then they can build additional experiences on top.
Creating components from JavaScript
We will need to offer new APIs to create components with JavaScript. These APIs will be exposed from the global Blazor object, and will return an object developers can use to further interact with the component.
Removing/destroying components from JavaScript
We will need to offer some APIs to destroy existing components and free up resources once the components are no longer being used. These APIs can be exposed directly through the proxy returned by the renderComponent method. For example:
We will name this method dispose since it is a convention known from .NET developers. Disposing a component this way will free up all .NET resources in use by the component as well as offer an opportunity for the component and its descendants to free up all DOM resources/state they were using via the standard dispose mechanism.
Pass parameters to the component
We will need to pass parameters from JavaScript to the component during the initial render as well as during successive renders. In general, there is no strongly typed contract for passing in parameters to a component. Since a component receives a
ParameterView
instance which is comparable to a list ofIEnumerable<KeyValuePair<string,object>>
where the key is the name of the parameter and the value is represented as an object. However, during deserialization we need to know the types of the parameters or otherwise we won't be able to correctly deserialize them. In the case of server-side Blazor we also want to make sure we don't deserialize any unknown parameters and create the possibility of a type explosion.Based on the points made above, we need to require some way of registering what parameters a "dynamic" root component is willing to accept. When a component receives parameters from JavaScript there are four cases to consider:
InvokeAsync("", ..args))
.JSFunction
that represents a JavaScript function that is directly invokablejsFunc.InvokeAsync(...args)
Parameter, CascadingParameter, etc.
to automatically detect the parameters a component is willing to accept.Parameter
andCascadingParameter
attributes.CascadingParameter
just as a regularParameter
attribute since there is no cascading value provider wrapping it. Its valuable to make it work because that way avoids having to wrap those components just for using them as root components.The gauge here is how much we want to make these types of components work in general situations without requiring explicit changes to the component definition or requiring additional steps to register the component parameters.
Pass information out from the component to JavaScript
There are already several ways to do this, for example doing JS interop from the component. However, is a common pattern that elements/component can communicate with other components via "event-like" interfaces. In this sense there are several options:
Action, Function, EventCallback
.The first two options are already supported by Blazor, the third option is not, however it is a common way to interact with elements on a page, so it is interesting we consider supporting it.
Expose an "interface" from the component to the "JavaScript" world that can be consumed through "idiomatic" javascript code.
Given that these components will have a higher level of interaction with JavaScript, it might make sense to offer an interface that can naturally be directly consumed via JavaScript. For example, if we are building a "form" component in Blazor, it might make sense that it offers a set of methods to interact with the component through JavaScript, like
validate
,reset
,submit
, etc.We can consider a new feature that components can implement to offer this "stream-lined" interface to the JavaScript world. We can mark the component somehow to "expose" some methods directly on the JS proxy created as a result of rendering a new instance of the component and we can use that gesture to also make the component re-render automatically after one of those methods is called in the same way it happens when a component handles an event.
Going back to the example above, it means that when a component receives a call to
validate
,reset
orsubmit
it can avoid an explicit call toStateHasChanged
.Content inside existing elements
So far we've described components that don't have "content" inside them. However there can be situations where it is useful to have content within them, for example if the layout for a page/section is done in Blazor and the content is done with a different framework.
Should Blazor offer a way to represent these inner nodes and pass those to the component upon initialization?
An HTML element node could be abstracted into a
RenderFragment
and rendered with the same syntax as one, with the exception that the content is already provided by someone else. There would need to be a way to represent these special "markup" primitives in a render batch and additional handling on the browser renderer to find the given html element and insert it in the dom at the right position.This opens the door to more complex interactions between frameworks than just "islands" within frameworks.
Phase two - Integration with other frameworks
This phase focuses on features and fixes to ensure it is possible to integrate properly with other UI frameworks. We need to establish the requirements we can impose on other frameworks to ensure we can integrate with them.
For example, when we render a component into a DOM element it is fundamental that Blazor has full control over the rendering and updates of that element and that the presence of additional children created by Blazor doesn't interfere with the other framework rendering process as well as updates from the other framework don't interfere with the rendered nodes for the element.
In this sense, any framework that wants to be supported here needs to be able to:
At this stage we will also ensure that Blazor is also able to integrate with popular web standards in this space (web components related standards) like custom elements, shadow DOM or slotted content.
As a result, we should be able to:
shadow root
.slotted
content inside the element where we are rendering the component.By supporting the standards associated with web components we can make sure that developers can author a set of components that cleanly integrate with any other framework.
Phase three - Smooth out the experience
This is additional work that we can choose to do if we have time and that will cover the remaining experience gaps around authoring component libraries that can be consumed by other frameworks.
Plan of work
Given the large amount of work in this space we will break down the work into smaller pieces of work that can be implemented in smaller chunks of work.
Drawbacks
Considered alternatives
Open questions
The text was updated successfully, but these errors were encountered: