

Blazor Component Callback from a RenderFragment Template
source link: https://www.codeproject.com/Articles/5329938/Blazor-Component-Callback-from-a-RenderFragment-Te
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

The Problem
I was working on a Blazor Component that needed to support external button(s) in the footer template that interacted with method(s) in the Component.
Typically, the Component would look something like:
<div> <h1>@HeaderText</h1> <p>@BodyText</p> @if (FooterTemplate is not null) { @FooterTemplate } </div> @code { [Parameter] public string HeaderText { get; set; } [Parameter] public string BodyText { get; set; } [Parameter] public RenderFragment? FooterTemplate { get; set; } private void OnClicked() { // do something here } }
Then we would use the Component something like:
<ComponentName> <FooterTemplate> <button @onclick="OnClicked">Close</button> </FooterTemplate> </ComponentName>
The issue here is that the button @onclick
will call a local method and not call the method in the component.
The Solution
This article focuses on a solution using RenderFragment<>
and EventCallback
/EventCallback<>
to enable calling methods in the component from external templates. We will also look at how to pass parameters back using the same solution.
TL;DR
Downloadable code for the solution can be found at the end of the article.
EventCallback
- Official Definition: A bound event handler delegate.
- Blazor University: The
EventCallback
class is a specialBlazor
class that can be exposed as a Parameter so that components can easily notify consumers when something of interest has occurred. When building Components with Data binding, we use anEventCallback
to notify that a property has changed.
RenderFragment
Official Definition: A RenderFragment
represents a segment of UI to render. RenderFragment<TValue>
takes a type parameter that can be specified when the render fragment is invoked.
Implementation
When we look at the code for the constructor and the InvokeAsync
method in EventCallback
class, it is defined as follows:
public EventCallback(IHandleEvent? receiver, MulticastDelegate? @delegate) { this.Receiver = receiver; this.Delegate = @delegate; } public Task InvokeAsync(object? arg) => this.Receiver == null ? EventCallbackWorkItem.InvokeAsync<object>(this.Delegate, arg) : this.Receiver.HandleEventAsync( new EventCallbackWorkItem(this.Delegate), arg); public Task InvokeAsync() => this.InvokeAsync((object) null);
What interests us here is that we can pass a method in on initialization and pass parameters (optionally) when invoked.
RenderFragment<TValue>
allows us to expose objects/classes to the Template for the Component. We can now change the above problem code as follows:
- Component:
Copy Code
<div> <h1>@HeaderText</h1> <p>@BodyText</p> @if (FooterTemplate is not null) { @FooterTemplate(new EventCallback(null, OnCallbackClicked)) } </div> @code { [Parameter] public string HeaderText { get; set; } [Parameter] public string BodyText { get; set; } [Parameter] public RenderFragment<EventCallback>? FooterTemplate { get; set; } private void OnCallbackClicked() { // do something here } }
- Usage:
Copy Code
<ComponentName> <FooterTemplate> <button @onclick="async () => await context.InvokeAsync().ConfigureAwait(false)"> Close </button> </FooterTemplate> </ComponentName>
We can simplify this code using Method Group:
Copy Code<ComponentName> <FooterTemplate> <button @onclick="context"> Close </button> </FooterTemplate> </ComponentName>
So How Does This Work?
context
is the EventCallback
passed from the Component to the Template. When the Template button is pressed, the InvokeAsync
method is invoked on the context
and executes the delegate OnCallbackClicked
method in the Component.
With the Method Group simplification above, the compiler automagically knows to call the InvokeAsync
on the context
(EventCallback
) class as defined in the EventCallback
class..
What if We Want to Pass Parameters Back to the Component?
We use the generic EventCallback<T>
. To pass one (1) or more parameters. For this, we will use an argument class.
- Arguments:
Copy Code
public interface IElementCallbackArgs { /* base interface */ } public class MessageCallbackArgs : IElementCallbackArgs { public string? Message { get; set; } }
- Component:
Copy Code
<div> <h1>@HeaderText</h1> <p>@BodyText</p> @if (FooterTemplate is not null) { @FooterTemplate( new EventCallback<IElementCallbackArgs>( null, new Action<MessageCallbackArgs> (args => OnCallbackClicked(args)))) } </div> @code { [Parameter] public string HeaderText { get; set; } [Parameter] public string BodyText { get; set; } [Parameter] public RenderFragment<EventCallback<IElementCallbackArgs>>? FooterTemplate { get; set; } private void OnCallbackClicked(MessageCallbackArgs args) { // do something here } }
Again, we can use Method Groups to simplify the code:
Copy Code<div> <h1>@HeaderText</h1> <p>@BodyText</p> @if (FooterTemplate is not null) { @FooterTemplate( new EventCallback<IElementCallbackArgs>( null, OnCallbackClicked)) } </div>
- Usage:
Copy Code
<ComponentName> <FooterTemplate> <button @onclick="async () => await OnClickedAsync(context)"> Close </button> </FooterTemplate> </ComponentName> @code { private static async Task OnClickedAsync( EventCallback<IElementCallbackArgs> callback) => await callback.InvokeAsync( new MessageCallbackArgs { Message = "message goes here" }).ConfigureAwait(false); }
So like the first callback only, here we are doing the same invocation, however we are passing back data specific to the event / button press.
Improvements
The code as-is works as expected. What we can do is encapsulate the EventCallback
in a wrapper interfaces and classes. The code below is a base implementation that allows to be expanded upon.
- Definition:
Copy Code
public interface IElementCallback { EventCallback Execute { get; } } public interface IElementCallback<T> { EventCallback<T> Execute { get; } }
- Implementation:
Copy Code
public class ElementCallback : IElementCallback { public ElementCallback(MulticastDelegate @delegate) => Execute = new EventCallback(null, @delegate); public EventCallback Execute { get; } } public class ElementArgsCallback : IElementArgsCallback { public ElementArgsCallback(MulticastDelegate @delegate) => Execute = new EventCallback<IElementCallbackArgs>(null, @delegate); public EventCallback<IElementCallbackArgs> Execute { get; } }
Example 1 - Basic
The following example has a button inside the component and a button in the template. This is to simulate when a component could have a preset button or allow for an optional custom button.
Component:
BasicComponent
Shrink ▲ Copy Code<button type="button" class="btn btn-primary me-4" @onclick="ButtonClicked"> OK </button> @if (ContentTemplate is not null) { @ContentTemplate(new ElementCallback(OnCallbackClicked)) } <hr /> <ul> @if (!Messages.Any()) { <li>No buttons clicked...</li> } else { @foreach (string message in Messages) { <li>@message</li> } } </ul> <hr /> @code { [Parameter] public RenderFragment<IElementCallback>? ContentTemplate { get; set; } private void ButtonClicked() => Clicked($"Button clicked in {nameof(BasicComponent)}"); private void OnCallbackClicked() => Clicked("External button clicked"); private readonly IList<string> Messages = new List<string>(); private void Clicked(string message) { Messages.Add(message); InvokeAsync(StateHasChanged); } }
- Implementation
Copy Code
<h2>Example 1 - Simple ElementCallback</h2> <BasicComponent> <ContentTemplate> <button type="button" class="btn btn-outline-success" @onclick="context.Execute"> Template OK </button> </ContentTemplate> </BasicComponent>
- Output:
Example 2 - Arguments
This example expands on the first and passes back a message to the component.
Component:
BasicComponent
Shrink ▲ Copy Code<button type="button" class="btn btn-primary me-4" @onclick="ButtonClicked"> OK </button> @if (ContentTemplate is not null) { @ContentTemplate(new ElementArgsCallback(OnCallbackClicked)) } <hr /> <ul> @if (!Messages.Any()) { <li>No buttons clicked...</li> } else { @foreach (string message in Messages) { <li>@message</li> } } </ul> <hr /> @code { [Parameter] public RenderFragment<IElementArgsCallback>? ContentTemplate { get; set; } private void ButtonClicked() => Clicked($"Button clicked in {nameof(ArgsComponent)}"); private void OnCallbackClicked(MessageCallbackArgs args) => Clicked(args.Message ?? "External button clicked"); private readonly IList<string> Messages = new List<string>(); private void Clicked(string message) { Messages.Add(message); InvokeAsync(StateHasChanged); } }
- Implementation
Copy Code
<h2>Example 2 - Message ElementCallback</h2> <ArgsComponent> <ContentTemplate> <button type="button" class="btn btn-outline-success" @onclick="@(async () => await ClickedAsync(context.Execute))"> Template OK </button> </ContentTemplate> </ArgsComponent> @code { private int _count = 1; private async Task ClickedAsync( EventCallback<IElementCallbackArgs> callback) => await callback.InvokeAsync( new MessageCallbackArgs { Message = $"Message > Click # {_count++}" }).ConfigureAwait(false); }
- Output:
Working Example
Below is a link to the code used from concept to final implementation as used when I initially looked at the problem and is mentioned in the article above.
Summary
The final solution for enabling Component method callback, with optional data, from an external Template delivers a clean implementation that can be easily expanded on for any use case.
Enjoy!
History
- v1.0 - 17th April, 2022 - Initial release
Recommend
-
6
How to send callback method to JSInterop in Blazor ? Blazor client-side or server side can handle only CPU bound calculation. For every interaction with the browser you need to use JSInterop. Even on Blazor itself the team uses JSIn...
-
12
Introduction This article started life as some code I wrote to explore what I thought I already understood - the mechanics of Blazor Component Rendering. Repo and Site The code is available in the
-
11
Build a Blazor 'Copy to Clipboard' component with a Markdown editor Let's build a 'Copy to Clipboard' component, where we can copy Mar...
-
3
Blazor and Contentful CMS Blog TemplateSkip to main content Cody Merritt Anhorn...
-
10
Authentication Support in Syncfusion Blazor Template Studio: A Complete GuideAuthentication is the process of determining a user’s identity and verifying whether the user has access to a resource. You can define...
-
11
Share on twitter Share on facebook Share on linkedin
-
3
binwen on Twitter: "Hot-reload is in Fun.Blazor v2 beta now. Nuget template is also updated. #fsharp #blazor Here is the journey for how I implement it in a limited way: https://t.co/cdQHFVIQAx https://t.co/vzLBJ6JzBq" Don’t miss what’s h...
-
5
.NET 8 Preview 5 Combines Upcoming Blazor Changes in Handy New Project Template ...
-
9
Unified Blazor Web App Project Template Fully Explained
-
4
Interactive what now? Deciphering Blazor's web app project template optionsFebruary 13, 2024 · 4 minute read · Tags: aspnet |
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK