` It's relatively easy to do this:
<h3>DivContainerComponent</h3>
<div class="bg-dark text-white m-3 p-2">
@ChildContent
</div>
@code {
[Parameter] public RenderFragment? ChildContent { get; set; }
}
However, there are situations where this doesn't quite fit.
Forms are a good example. You want to impose a standard edit or view form wrapper to all your data forms. It's just the internal field definitions that are specific. You can use the method above, but it's clumpsy and contains repetitive code.
Unfortunately the standard Blazor component wasn't designed for this. There are workarounds, but none are nice.
Blazr.Demo.ComponentWrapper Repo
A new component is needed to implement this cleanly. The base component we use here is my lean, mean, green component. There's a reference link in the Appendix.
First this is a wrapper in action. Note:
BuildRenderTree
which we ignore.Wrapper
render fragment.@this.Content
is where the content from the child will be rendered.@inherits UIWrapperComponentBase @code { protected override RenderFragment Wrapper => (__builder) => { <div class="bg-primary text-white p-3 b-2"> @this.Content </div> }; }
Note:
Wrapper
defined as abstract. It must be implemented in child classes.this.BuildRenderTree(builder)
is assigned to Content
. this.BuildRenderTree(builder)
contains the compiled Razor code that represents the child component's content.renderFragment
for performance. If not hidden it renders the contents of Wrapper
if it's not null, or the child content directly.public abstract class UIWrapperComponentBase : UIComponentBase { protected virtual RenderFragment? Wrapper { get; } protected RenderFragment? Content => (builder) => this.BuildRenderTree(builder); public UIWrapperComponentBase() { this.renderFragment = builder => { hasPendingQueuedRender = false; hasNeverRendered = false; var hide = this.hide | this.Hidden; if (hide) return; if (this.Wrapper is not null) { this.Wrapper.Invoke(builder); return; } BuildRenderTree(builder); }; } }
UIComponentBase
doesn't implement any automated UI Event Handling. If you want ComponentBase
type handling you need to implement it yourself.
If you need automated UI rendering, implement IHandleEvent
.
For a single render:
@implements IHandleEvent //... @code { public async Task HandleEventAsync(EventCallbackWorkItem callback, object? arg) { await callback.InvokeAsync(arg); StateHasChanged(); } }
For the ComponentBase
double event:
@implements IHandleEvent //... @code { public async Task HandleEventAsync(EventCallbackWorkItem callback, object? arg) { var task = callback.InvokeAsync(arg); if (task.Status != TaskStatus.RanToCompletion && task.Status != TaskStatus.Canceled) { StateHasChanged(); await task; } StateHasChanged(); } }
If you need to implement the OnAfterRender
event, implement IHandleAfterRender
.
@implements IHandleAfterRender //... @code { private bool _hasCalledOnAfterRender; public Task OnAfterRenderAsync() { var firstRender = !_hasCalledOnAfterRender; _hasCalledOnAfterRender |= true; // your code here return Task.CompletedTask; } }
Here's a simple demo setting Index
to inherit from MyWrapper
.
The result: