In this example, we build a reusable Blazor component using Syncfusion’s SfMultiSelect that supports:
- Dynamic option filtering based on a Picklist type (e.g., Color, Unit)
- Two-way data binding with a parent component
- Internal state management to avoid unnecessary updates
đź’ˇ Why This Pattern?
Often in enterprise forms, you need a dynamic dropdown that updates based on a type and preselects values passed from a parent. This setup ensures:
- Clean encapsulation
- Correct @bind support
- Lightweight rendering
âś… Component Markup
<SfMultiSelect TValue="List<PicklistSetDto>"
TItem="PicklistSetDto"
@ref="multiObj"
DebounceDelay="@NumericValue"
Placeholder="@Label"
DataSource="@Options"
@bind-Value="InternalSelectedValues"
Mode="VisualMode.Box"
AllowFiltering="true"
FilterType="Syncfusion.Blazor.DropDowns.FilterType.Contains"
PopupHeight="300px">
<MultiSelectFieldSettings Text="Text" Value="Value" />
<MultiSelectEvents TValue="List<PicklistSetDto>" TItem="PicklistSetDto" Filtering="OnFilter"></MultiSelectEvents>
</SfMultiSelect>
đź§ Component Logic
@code {
/// <summary>
///
/// </summary>
SfMultiSelect<List<PicklistSetDto>, PicklistSetDto> multiObj { get; set; }
/// <summary>
/// /
/// </summary>
private int NumericValue = 300;
// Label to show in the input placeholder
[Parameter]
public string Label { get; set; } = "Select Options";
// The Picklist type (e.g., Color, Unit, etc.)
[Parameter]
public Picklist Name { get; set; }
// Value passed from the parent component
[Parameter]
public IEnumerable<PicklistSetDto> SelectedValues { get; set; } = new List<PicklistSetDto>();
// EventCallback to notify parent of selection changes
[Parameter]
public EventCallback<IEnumerable<PicklistSetDto>> SelectedValuesChanged { get; set; }
// List of options to be shown in the dropdown
protected List<PicklistSetDto> Options = new();
// Backing field for internal selected values
private List<PicklistSetDto> _internalSelectedValues = new();
private Picklist _previousName;
// Internal property used for binding to the MultiSelect component
private List<PicklistSetDto> InternalSelectedValues
{
get => _internalSelectedValues;
set
{
// Only update if the list actually changed (prevents unnecessary updates)
if (!_internalSelectedValues.SequenceEqual(value ?? new()))
{
_internalSelectedValues = value ?? new();
// Notify parent of the new selected values
SelectedValuesChanged.InvokeAsync(_internalSelectedValues);
}
}
}
// Initialize the list of options and apply any preselected values
protected override async Task OnInitializedAsync()
{
// Load options based on the Picklist type (Name)
Options = await Task.FromResult(PicklistService.DataSource
.Where(x => x.Name == Name)
.OrderBy(x => x.Name)
.ToList());
}
// Ensure selected values are refreshed if parameters change after initialization
public override async Task SetParametersAsync(ParameterView parameters)
{
await base.SetParametersAsync(parameters);
if (!EqualityComparer<Picklist>.Default.Equals(_previousName, Name))
{
_previousName = Name;
PicklistService.Refresh();
Options = PicklistService.DataSource
.Where(x => x.Name == Name)
.ToList();
InternalSelectedValues = Options
.Where(x => SelectedValues.Any(s => s.Value == x.Value))
.ToList();
}
}
private async Task OnFilter(FilteringEventArgs args)
{
args.PreventDefaultAction = true;
var query = new Query().Where(new WhereFilter()
{ Field = "Name",
Operator = "contains",
value = args.Text, IgnoreCase = true });
query = !string.IsNullOrEmpty(args.Text) ? query : new Query();
await multiObj.FilterAsync(this.Options, query);
}
}
đź§© Example Usage in Parent Component
<PicklistMultiSelect Label="Select Colors"
Name="Picklist.Color"
SelectedValues="@colorData"
SelectedValuesChanged="OnColorChanged" />
private List<PicklistSetDto> colorData = new();
private void OnColorChanged(IEnumerable<PicklistSetDto> values)
{
colorData = values.ToList();
}
To efficiently handle large datasets in Syncfusion’s SfMultiSelect in Blazor, you should avoid loading the entire dataset into memory and instead use server-side filtering and paging. Here's how to implement it:
âś… 1. Enable Virtualization
For large datasets, enable virtualization to load items on-demand:
<SfMultiSelect TValue="List<PicklistSetDto>"
TItem="PicklistSetDto"
@ref="multiObj"
Placeholder="@Label"
@bind-Value="InternalSelectedValues"
AllowFiltering="true"
EnableVirtualization="true"
PopupHeight="300px"
Filtering="OnFilter">
<MultiSelectFieldSettings Text="Text" Value="Value" />
</SfMultiSelect>
âš EnableVirtualization="true" improves rendering performance but requires paging logic in OnFilter.
âś… 2. Implement OnFilter with Server-Side Querying
Replace your in-memory filter with actual server-side filtering:
private async Task OnFilter(FilteringEventArgs args)
{
args.PreventDefaultAction = true;
// Server-side call (adjust as needed)
var filtered = await PicklistService.GetFilteredAsync(Name, args.Text);
await multiObj.FilterAsync(filtered, new Query());
}
âś… 3. Modify Your Service to Support Filtering and Paging
public async Task<List<PicklistSetDto>> GetFilteredAsync(Picklist name, string? filter)
{
using var context = _dbContextFactory.CreateDbContext();
var query = context.PicklistSets
.Where(p => p.Name == name);
if (!string.IsNullOrWhiteSpace(filter))
query = query.Where(p => p.Text.Contains(filter));
return await query
.OrderBy(p => p.Text)
.Take(100) // Limit for performance
.Select(p => new PicklistSetDto
{
Value = p.Value,
Text = p.Text,
Name = p.Name
})
.ToListAsync();
}
✅ 4. Optional – Add Debounce to Reduce API Calls
DebounceDelay="300"
This delays filtering execution until the user stops typing, avoiding too many API hits.
🚀 Why Use Syncfusion in Blazor?
Syncfusion’s Blazor UI components offer a wide range of benefits for modern web apps. Here’s why it’s a great choice for developers:
âś… 1. Rich Component Library
Syncfusion provides 80+ production-ready components, including:
SfGrid, SfMultiSelect, SfDatePicker, SfDialog, SfChart, and more
Covers everything from data entry to dashboards
⚡ 2. Performance Optimized
Built-in virtualization and lazy loading
Handles large datasets smoothly (e.g., thousands of records in a Grid)
🎨 3. Consistent UI/UX
Beautiful, modern design out of the box
Theme support: Fluent, Bootstrap5, Material, Tailwind, or your custom styles
đź§° 4. Developer Productivity
Intuitive APIs that follow Blazor standards (@bind-Value, DataSource, events)
- Extensive documentation and real-world samples
- Native Blazor — not just JS interop wrappers
đź”’ 5. Enterprise-Ready Features
- Built-in support for localization, accessibility (WCAG 2.0), and RTL
- Works seamlessly with validation frameworks like FluentValidation
🔄 6. Excellent Integration with Modern Blazor Patterns
Compatible with @inject, CascadingParameter, and EventCallback
- MVVM-friendly (e.g., CommunityToolkit.MVVM)
- Supports reactive forms and dynamic configuration
👨‍💻 7. Strong Commercial Support
Regular updates and new feature rollouts
- Dedicated support via forums and ticket system
- Source code access included in paid plans
Top comments (2)
This is a great example of how to wrap Syncfusion controls with meaningful encapsulation in Blazor. The separation of state and parent binding is textbook clean.
Glad that you liked. Thanks 👍