Enrico
Enrico

Reputation: 6202

Pass value type to Blazor Component

I'm creating a Blazor component similar to DataTables and also I want to implement generic filters. For that, I have created 2 components for numbers: one integer number and another one for floating number.

From the main component, I want to pass type of number.

<NumberInput TNumber="short"
    Value="@FilterRule.FilterValue"
    OnValueChangedEvent="@FilterRule.UpdateFilterValue"
    IncludeApply="IncludeApply()"
    IsApplied="@FilterRule.IsApplied"
    AppliedText="@FilterRule.FilterValue!.ToString()"
    ApplyFilterEvent="(e) => ApplyFilter(FilterRule.Guid, true)"
    UnApplyFilterEvent="(e) => UnApplyFilter(FilterRule.Guid, true)"
    Attributes="Attributes"
    ApplyButtonCssClass="@ApplyButtonCssClass"
    InputCssClass="@InputCssClass"
    MaxWidth="MaxWidth" />

For example, TNumber can have short, int, long and so on. This is because I have a function that gives me the minimum and maximum values for each type of number.

public static Tuple<T, T> GetMinMaxValue<T>()
{
    object obj1 = (object)default(T);
    object obj2 = (object)default(T);

    if (obj1 == null || obj2 == null)
        return (Tuple<T, T>)null;

    switch (Type.GetTypeCode(typeof(T)))
    {
        case TypeCode.Int16:
            obj1 = (object)char.MinValue;
            obj2 = (object)char.MaxValue;
            break;
        case TypeCode.SByte:
            obj1 = (object)sbyte.MinValue;
            obj2 = (object)sbyte.MaxValue;
            break;
        // and so on...
    }
}

The NumberInput has this code:

<input class="@DefaultClass form-control-sm @InputCssClass" type="number" value="@Value"
       style="@(MaxWidth != 0 ? string.Format("max-width: {0}px", (object) this.MaxWidth) : "")"
       @onchange="@UpdateValue" step="any">

@code {
    [Parameter] public long Value { get; set; }
    [Parameter] public EventCallback<ValueChangedEventArgs> OnValueChangedEvent { get; set; }
    [Parameter] public string DefaultClass { get; set; } = "form-control";
    [Parameter] public bool IncludeApply { get; set; } = false;
    [Parameter] public bool IsApplied { get; set; } = false;
    [Parameter] public string AppliedText { get; set; } = "";
    [Parameter] public string InputCssClass { get; set; } = "";
    [Parameter] public string ApplyButtonCssClass { get; set; } = "";
    [Parameter] public Dictionary<string, object> Attributes { get; set; }
    [Parameter] public int MaxWidth { get; set; }
    [Parameter] public EventCallback<MouseEventArgs> ApplyFilterEvent { get; set; }
    [Parameter] public EventCallback<MouseEventArgs> UnApplyFilterEvent { get; set; }

    private async Task UpdateValue(ChangeEventArgs args)
    {
        Value = Convert.ToInt32(args.Value.ToString());
        await this.OnValueChangedEvent.InvokeAsync(new ValueChangedEventArgs((object)this.Value));
    }
}

So, the question is: how can I pass as parameter the type of variable I want to use?

Upvotes: 2

Views: 1836

Answers (1)

Amal K
Amal K

Reputation: 4854

The @typeparam directive


As Brian Parker hinted in the comments, the solution is to add a type parameter to the NumberInput component. In the NumberInput component, add the @typeparam Razor directive:

@typeparam TNumber 

This is nothing but a generic type parameter in disguise. You can now use this parameter anywhere in the NumberInput component just like you'd with a generic parameter in a C# type like a class or struct. Most of the time, the types of type parameters can be inferred and setting them explicitly is redundant. However, if the type cannot be inferred, it can be explicitly specified:

<NumberInput 
    TNumber="int"                                       @* Explicitly passing type *@
    Value="@FilterRule.FilterValue"
    OnValueChangedEvent="@FilterRule.UpdateFilterValue"
    IncludeApply="IncludeApply()"
    IsApplied="@FilterRule.IsApplied"
    AppliedText="@FilterRule.FilterValue!.ToString()"
    ApplyFilterEvent="(e) => ApplyFilter(FilterRule.Guid, true)"
    UnApplyFilterEvent="(e) => UnApplyFilter(FilterRule.Guid, true)"
    Attributes="Attributes"
    ApplyButtonCssClass="@ApplyButtonCssClass"
    InputCssClass="@InputCssClass"
    MaxWidth="MaxWidth" />

Avoid overwriting parameters


Also, as Mister Magoo pointed out, you should not overwrite to parameter values because if the container component calls StateHasChanged(), all the parameters of the child components will be reset to their original values and state will be lost. For more info, see this. To safely maintain state, copy the parameter value to a private field first, in OnInitialized or OnInitializedAsync:

private long _value;
protected override void OnInitialized()
{
        _value = Value;
}

The final NumberInput


Finally, NumberInput would look like this:

@typeparam TNumber

<input class="@DefaultClass form-control-sm @InputCssClass" 
       type="number" 
       value="@Value"
       style="@(MaxWidth != 0 ? string.Format("max-width: {0}px", (object) this.MaxWidth) : "")"
       @onchange="@UpdateValue" 
       step="any" />

@code {

    private long _value;

    [Parameter] public long Value { get; set; }
    [Parameter] public EventCallback<ValueChangedEventArgs> OnValueChangedEvent { get; set; }
    [Parameter] public string DefaultClass { get; set; } = "form-control";
    [Parameter] public bool IncludeApply { get; set; } = false;
    [Parameter] public bool IsApplied { get; set; } = false;
    [Parameter] public string AppliedText { get; set; } = "";
    [Parameter] public string InputCssClass { get; set; } = "";
    [Parameter] public string ApplyButtonCssClass { get; set; } = "";
    [Parameter] public Dictionary<string, object> Attributes { get; set; }
    [Parameter] public int MaxWidth { get; set; }
    [Parameter] public EventCallback<MouseEventArgs> ApplyFilterEvent { get; set; }
    [Parameter] public EventCallback<MouseEventArgs> UnApplyFilterEvent { get; set; }

    private async Task UpdateValue(ChangeEventArgs args)
    {
        _value = Convert.ToInt32(args.Value.ToString());
        await this.OnValueChangedEvent.InvokeAsync(new ValueChangedEventArgs((object)this.Value));
    }

    protected override void OnInitialized()
    {
        _value = Value;
    }
}

Upvotes: 3

Related Questions