0
votes

I have a need to use some custom inputs built as razor components on pre-existing views and pages, but can't seem to get it to work using the component tag helper. For example, the component code that I've been testing with (from https://chrissainty.com/creating-bespoke-input-components-for-blazor-from-scratch/) at first results in an exception because ValueExpression ends up being null (no options for binding using the tag helper, from what I can tell). If I then set ValueExpression myself, I end up with a json exception (object cycle detected). I think maybe because the mechanism for moving parameters from the tag helper to the underlying component doesn't support Func<> objects? Not sure.

Am I trying to use the tag helper incorrectly perhaps? I'm using it in other places to render self-contained components (like an entire EditForm), and that seems to be working fine, but how to get it working in this particular use case eludes me :(

Inside .cshtml file I want the control to render in:

<component type="typeof(MyComponent)" render-mode="ServerPrerendered" param-ValueExpression="(Func<string>)(() => LocalProperty)" />

MyComponent.razor

<input class="_fieldCssClasses" value="@Value" @oninput="HandleInput" />

@if (_showValidation) {
    <div class="validation-message">You must provide a name</div>
}

@code {
    private FieldIdentifier _fieldIdentifier;
    private string _fieldCssClasses => EditContext?.FieldCssClass(_fieldIdentifier) ?? "";
    private bool _showValidation = false;

    [CascadingParameter] private EditContext EditContext { get; set; }

    [Parameter] public string Value { get; set; }
    [Parameter] public EventCallback<string> ValueChanged { get; set; }
    [Parameter] public Expression<Func<string>> ValueExpression { get; set; }
    [Parameter] public bool Required { get; set; }

    protected override void OnInitialized() {
        _fieldIdentifier = FieldIdentifier.Create(ValueExpression);
    }

    private async Task HandleInput(ChangeEventArgs args) {
        await ValueChanged.InvokeAsync(args.Value.ToString());

        if (EditContext != null) {
            EditContext.NotifyFieldChanged(_fieldIdentifier);
        } else if (Required) {
            _showValidation = string.IsNullOrWhiteSpace(args.Value.ToString());
        }
    }
}
1

1 Answers

0
votes

So to resolve this, I just made a use-case-specific wrapper component around the inner component, making sure to also pass in an Id that the inner component will assign to the name and id attributes of the input it's rendering (allows it to "bind" when you submit the form), and then included the wrapper component on the page/view. The wrapper simply satisfies the dependencies of the inner component, which the component tag helper does not provide a mechanism for if the dependency is more complex (like a Func<> object).

MyComponentWrapper.razor:

<div class='purdy'>
    <MyComponent Id="@Id" @bind-Value="Value"></MyComponent>
</div>
...
@code {
    [Parameter]
    public string Id { get; set; }
    [Parameter]
    public string Value { get; set; }
    ...
}

MyContainingPage.cshtml:

<component type="typeof(MyComponentWrapper)" render-mode="ServerPrerendered" param-Id="myFormInput" param-Value="LocalProperty" />
...