Creating a Syncfusion MultiSelect Component with Two-Way Binding in Blazor

Leader posted 3 min read

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"
               Placeholder="@Label"
               DataSource="@Options"
               @bind-Value="InternalSelectedValues"
               Mode="VisualMode.Box"
               AllowFiltering="true"
               FilterType="FilterType.Contains"
               PopupHeight="300px">
    <MultiSelectFieldSettings Text="Text" Value="Value" />
</SfMultiSelect>

Component Logic

@code {
    [Parameter]
    public string Label { get; set; } = "Select Options";

    [Parameter]
    public Picklist Name { get; set; }

    [Parameter]
    public IEnumerable<PicklistSetDto> SelectedValues { get; set; } = new List<PicklistSetDto>();

    [Parameter]
    public EventCallback<IEnumerable<PicklistSetDto>> SelectedValuesChanged { get; set; }

    protected List<PicklistSetDto> Options = new();

    private List<PicklistSetDto> _internalSelectedValues = new();

    private List<PicklistSetDto> InternalSelectedValues
    {
        get => _internalSelectedValues;
        set
        {
            if (!_internalSelectedValues.SequenceEqual(value ?? new()))
            {
                _internalSelectedValues = value ?? new();
                SelectedValuesChanged.InvokeAsync(_internalSelectedValues);
            }
        }
    }

    protected override async Task OnInitializedAsync()
    {
        Options = await Task.FromResult(PicklistService.DataSource
            .Where(x => x.Name == Name)
            .ToList());
    }

    public override async Task SetParametersAsync(ParameterView parameters)
    {
        await base.SetParametersAsync(parameters);

        InternalSelectedValues = Options
            .Where(x => SelectedValues.Any(s => s.Value == x.Value))
            .ToList();
    }
}

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:

<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:

✅ 1. Enable Virtualization

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

This delays filtering execution until the user stops typing, avoiding too many API hits.

DebounceDelay="300"

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)

  1. Consistent UI/UX
    Beautiful, modern design out of the box

Theme support: Fluent, Bootstrap5, Material, Tailwind, or your custom styles

  1. 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

  1. Enterprise-Ready Features

Built-in support for localization, accessibility (WCAG 2.0), and RTL

Works seamlessly with validation frameworks like FluentValidation

  1. Excellent Integration with Modern Blazor Patterns

Compatible with @inject, CascadingParameter, and EventCallback

MVVM-friendly (e.g., CommunityToolkit.MVVM)

Supports reactive forms and dynamic configuration

  1. Strong Commercial Support

Regular updates and new feature rollouts

Dedicated support via forums and ticket system

Source code access included in paid plans

If you read this far, tweet to the author to show them you care. Tweet a Thanks

Spyros Quick thought: have you considered handling updates to the Options list when the parent Name changes at runtime, or is that out of scope for this use case?

Thanks for the note I really appreciate it. We're all students of learning, after all.

In this case, the Name value remains static throughout the component’s lifecycle, so updating the Options dynamically isn’t necessary. However, you’re absolutely right: if we ever need to support changes to Name at runtime, we should definitely handle it properly.

Here’s one way to achieve that:

private Picklist _previousName;

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();
    }
}

More Posts

State Management Made Easy with Fluxor in Blazor

Spyros - Apr 25

How to Bind a MudSelect from an External Source in Blazor Using MudBlazor

Spyros - Apr 10

A Comprehensive Guide to Building Web Applications with Blazor

topuzas - Mar 14

Why Records in C# Are Great (and So Nice to Work With)

Spyros - May 10

Optimistic vs. Pessimistic Concurrency in EF Core (with Table Hints)

Spyros - Mar 31
chevron_left