fluentui-blazor icon indicating copy to clipboard operation
fluentui-blazor copied to clipboard

[dev-v5] Migrate AutoComplete component

Open MarvinKlein1508 opened this issue 3 months ago • 7 comments

MarvinKlein1508 avatar Sep 30 '25 17:09 MarvinKlein1508

@dvoituron it would be really great if you can also bind List<T> or Array<T> to this component. In v4 your property needs to be of type IEnumerable<T>. If you declared your property as List<T> you cannot bind it to the component.

MarvinKlein1508 avatar Sep 30 '25 17:09 MarvinKlein1508

List<T> inherits the IEnumerable<T>. This is the base type.

dvoituron avatar Sep 30 '25 17:09 dvoituron

@dvoituron yes I know. But you cannot bind to it in v4. Try it for yourself:

@inject DataSource Data

<FluentAutocomplete TOption="Country"
                    AutoComplete="off"
                    Autofocus="true"
                    Label="Select a country"
                    Width="250px"
                    MaxAutoHeight="@(AutoHeight ? "200px" : null)"
                    Placeholder="Select countries"
                    OnOptionsSearch="@OnSearchAsync"
                    OptionDisabled="@(e => e.Code == "au")"
                    MaximumSelectedOptions="5"
                    OptionText="@(item => item.Name)"
                    @bind-SelectedOptions="@SelectedItems" />

<p>
    <b>Selected</b>: @(String.Join(" - ", SelectedItems.Select(i => i.Name)))
</p>
<p>
    <FluentSwitch @bind-Value="@AutoHeight" Label="Auto Height" /><br />
    When the <code>MaxAutoHeight</code> attribute is set, the component adapts its height in relation to the selected elements.
</p>
@code
{
    bool AutoHeight = false;
    List<Country> SelectedItems = [];

    private async Task OnSearchAsync(OptionsSearchEventArgs<Country> e)
    {
        var allCountries = await Data.GetCountriesAsync();
        e.Items = allCountries.Where(i => i.Name.StartsWith(e.Text, StringComparison.OrdinalIgnoreCase))
                              .OrderBy(i => i.Name);
    }
}
Argument 2: cannot convert from 'Microsoft.AspNetCore.Components.EventCallback<System.Collections.Generic.List<FluentUI.Demo.Shared.SampleData.Country>>' to 'Microsoft.AspNetCore.Components.EventCallback'

MarvinKlein1508 avatar Sep 30 '25 17:09 MarvinKlein1508

I don't think that's a good idea. IEnumerable is the base type. Why arbitrarily choose List<T> or Array<T>? The “problem” will be the same with all other collection objects. So it's always best to use the base type.

dvoituron avatar Sep 30 '25 18:09 dvoituron

@dvoituron In my case I was using List<T> every time when I explicitly want to know within my object that this is a collection where stuff can be added.

For example I have some filters like this:

public sealed class BerichteFilter : PageFilterBase
{
    public string Kurzbezeichnung { get; set; } = string.Empty;
    public IEnumerable<BerichtStatus> Stati { get; set; } = [];
    public int UserId { get; set; }
    public bool NurEigeneAnzeigen { get; set; }
    public List<string> SachbearbeiterNummern { get; } = [];

    public IEnumerable<User> Anlageuser { get; set; } = [];
    public int? BerichtId { get; set; }
    public IEnumerable<Berichtart> Berichtarten { get; set; } = [];
    public IEnumerable<AddressType> AddressTypes { get; set; } = [];
    public DateTime? AnlageDatum { get; set; }
    public IEnumerable<Messe> Messen { get; set; } = [];
}

As you can see I have 5 IEnumerable<T> here. This indicates that I use them within a FluentAutoComplete. In some cases I have other filters like this where the IEnumerable<T> is pre-defined and I only use the component to display the values. Now you have to remember which of this properties are actually List<T> or Array<T> and you have to cast them every time you want to modify it outside of the component itself.

Perhaps you know a way how you can still use IEnumerable<T> but still use all types of implementations here.

MarvinKlein1508 avatar Sep 30 '25 18:09 MarvinKlein1508

I would say that your code should also use interfaces rather than List<T>.

In summary (helped by Copilot 😊)...

✅ Why Prefer Abstractions?

1. Flexibility and Maintainability Using abstractions allows you to change the underlying implementation without affecting the consuming code. For example, you can switch from List<T> to LinkedList<T> or a custom collection without breaking the API contract. [c# - Why r...e type ...], [Guidelines...Guidelines]

2. Encapsulation and Clean API Design Concrete types expose many methods that may not be relevant or safe for consumers. For instance, List<T> includes BinarySearch, Sort, and RemoveAt, which might not be appropriate for all use cases. Abstractions help hide unnecessary details and enforce cleaner usage. [Guidelines...Guidelines]

3. Improved Testability Interfaces are easier to mock in unit tests. This leads to looser coupling and better separation of concerns, which is especially valuable in large-scale systems like those you work on. [Prefer Int...yteCrafted]

4. Better Performance in Some Cases Using well-known empty collection instances (e.g., via EmptyIfNull() from R9Docs) can reduce memory usage and improve performance by avoiding allocations. [R9Docs]

🚫 What to Avoid

dvoituron avatar Sep 30 '25 18:09 dvoituron

Or make a component with :

@typeparam TItem
@typeparam TCollection where TCollection : IEnumerable<TItem>

But then you have to provide an extra parameter instead of doing casts ;D

Tyme-Bleyaert avatar Sep 30 '25 19:09 Tyme-Bleyaert