Open RdJNL opened 7 months ago
I am wondering why it was named <text>
, it doesn't make any sense and in Blazor it stops us from having a <Text>
component. Something like <fragment>
, <root>
or even <razorfragment>
would have been way more understanable and non-invasive.
In React it's <>
or <React.Fragment>
In Vue it's <template>
In Angular it's <ng-container>
Feature request
What
Writing methods that return
RenderFragment
is a way to extract parts of a component's template and potentially allow them to be virtual/abstract. I've tried multiple ways to write such a method (see below), but every way comes with its own disadvantages. It would be nice if there's a syntax for these methods that has none of these disadvantages.Why
As a C#/OO programmer, I've always been annoyed by the way templates interact with component inheritance in frontend code. When inheriting a base component, there's typically two choices: either keep the base component's template entirely unchanged or create a new template from scratch.
In my opinion, the solution to this is the use of virtual/abstract methods to create part of the template. A subtype can then replace parts of the template without having to rewrite the whole template. Blazor allows this by creating methods that return
RenderFragment
.Implementing those methods is best done in a .razor file, because in a C# file, you need to work with
RenderTreeBuilder
directly.The current options
I've tried multiple options, but none of them are satisfying.
Option 1 (one tag)
This option is pretty nice if the RenderFragment consists of only one HTML tag. For some reason Visual Studio indents the HTML by only 4 spaces, despite the
return
being at 8 spaces.Option 2 (
<text>
)This option allows multiple lines of HTML mixed with C# code like a normal Razor template. In my opinion, having to put the
<text>
tag around the HTML is ugly and it also means that whitespace is preserved in the final HTML. Visual Studio messes up the formatting even more this time.Option 3 (lambda with
__builder
)This option is my favorite from the ones I've found, but it still has disadvantages. For starters, you need to write a lambda, making this more verbose than the previous options. Also, the parameter for the lambda must be named
__builder
. The code within the body of the lambda is treated as a Razor code block (i.e.@{ }
). That means that plain text must be prefixed by@:
and e.g. for loops don't need to be prefixed by@
.Option 4 (the method itself is a
RenderFragment
)I'll add this last option, even though I think it's very impractical. Here, we don't return a
RenderFragment
from the method, but the method is aRenderFragment
(RenderFragment
is a delegate). The first 3 options could handle zero or more parameters. This option cannot handle any parameters. Also, when called without cast, there's a CS8974 warning, which isn't actually there in the generated C# file. Like option 3, the body of the method is treated like a Razor code block. Once again the parameter must be named__builder
.Remarks
Example components
Demo of super and sub class that use this mechanism (using option 3)
LoaderBase.razor: ```razor @if(!HasLoaded) { @:Loading... } else { @Content() } @code { protected abstract RenderFragment Content(); } ``` LoaderBase.razor.cs: ```csharp public abstract partial class LoaderBase { protected bool HasLoaded { get; private set; } protected sealed override async Task OnParametersSetAsync() { HasLoaded = false; await LoadAsync(); HasLoaded = true; } protected abstract Task LoadAsync(); } ``` SomeComponent.razor: ```razor @inherits LoaderBase @{ base.BuildRenderTree(__builder); } @code { protected override Task LoadAsync() { return Task.Delay(5000); } protected override RenderFragment Content() { return __builder => { @:Done loading! }; } } ```