skip to Main Content

I have a custom element (MdTextField that renders Material Web’s <md-filled-text-field>) that acts similar to <input />. Now the problem is, when using @bind-Value, what Blazor does is setting its value HTML attribute and not element (DOM) property. This does not work for the following case (please note I use input here to demonstrate the behavior, the custom component acts exactly like this standard input):

  • An input value is not updated when its value attribute is updated, for example: input.setAttribute("value", "new value");. This happen after the input value is changed by users.

  • Its value is changed if its property is set programmatically, for example: input.value = "new value".

document.querySelector(".btn1").addEventListener("click", () => {
  document.querySelector("input").setAttribute("value", "");
});

document.querySelector(".btn2").addEventListener("click", () => {
  document.querySelector("input").value = "";
});
<p>Edit the input below and try the buttons</p>

<input value="123" />
<button class="btn1">Clear value</button>
<button class="btn2">Correctly clear value</button>

Update: I just realized simply changing the value of the input and then click the button and it would not work at all. Basically the button doesn’t work if the input was changed.

Here, when you click the Clear button, it works once, then if you change the <input> again, the value attribute doesn’t change so if you click the button the 2nd time it doesn’t work anymore. In fact, the value is of the input is still the value that user entered.

In Javascript, I can simply use txt.value = "" but it’s not simple in Blazor.

I can solve the issue if I can do either of these:

  • Somehow use @bind="variable" in Blazor for MdTextField <md-filled-text-field> or any custom tag like how it is working for standard input.

  • Tell Blazor that @bind-Value should set the value property and not attribute, similar to Lit’s Property Expressions:

    html`<my-list .listItems=${this.items}></my-list>`;
    

I know if all else fails, I can use Javascript to solve the problem but I’d rather avoid it if possible.


Here’s an example component:

@* MyTextField.razor *@

@* Following HH's answer: *@
<md-filled-text-field @bind-value="Value" @bind-value:event="onchange" @bind-value:after="OnAfterValueChanged">
    @(ChildContent)
</md-filled-text-field>

@* My original code: *@
<md-filled-text-field value="@(Value)" @onchange="OnValueChanged">
    @(ChildContent)
</md-filled-text-field>

@code {

    [Parameter]
    public RenderFragment? ChildContent { get; set; }

    [Parameter, EditorRequired]
    public string Value { get; set; } = "";

    [Parameter]
    public EventCallback<string> ValueChanged { get; set; }

    async Task OnValueChanged(ChangeEventArgs e)
    {
        await ValueChanged.InvokeAsync(e.Value?.ToString() ?? "");
    }

    async Task OnAfterValueChanged()
    {
        await ValueChanged.InvokeAsync(Value);
    }
}

And usage:

@page "/test"

<MyTextField @bind-Value="text" />

<button @onclick="@(() => text = "")">Clear</button>

@code {

    string text = "123";

}

2

Answers


  1. How about

    <InputText @bind-Value="@text" /> <br/>
    <input @bind-value="@text"/>
    <button @onclick='() => text=""' >clear</button>
    
    @code
    {
        string text = "abc";
    }
    
    

    I have include both an <input> and an <InputText> , the latter should be the same as a <MdTextField> .

    Login or Signup to reply.
  2. Not final, but. My my-input [a quick and dirty mock for md-filled-text-field] isn’t working like it should, probably reflecting my rudimentary JS coding skills. Perhaps someone reading this will help me out?

    What it demonstrates is how to assign stuff in C# rather than Razor markup. It compiles and runs!

    It’s in DotNet 8.

    My App which adds the custom element.

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <base href="/" />
        <link rel="stylesheet" href="bootstrap/bootstrap.min.css" />
        <link rel="stylesheet" href="app.css" />
        <link rel="stylesheet" href="SO77583025.styles.css" />
        <link rel="icon" type="image/png" href="favicon.png" />
        <HeadOutlet @rendermode="@InteractiveServer" />
    </head>
    
    <body>
        <Routes @rendermode="@InteractiveServer" />
        <script src="_framework/blazor.web.js"></script>
        <script>
            class MyInput extends HTMLInputElement {
                constructor() {
                    super();
                }
                // Element functionality written in here
            }
    
            customElements.define("my-input", MyInput);
        </script>
    </body>
    
    </html>
    

    And then my Home:

    @page "/"
    @using Microsoft.AspNetCore.Components
    @using Microsoft.AspNetCore.Components.Rendering
    <PageTitle>Home</PageTitle>
    
    <h1>Hello, world!</h1>
    
    @CustomInput("input")
    
    @CustomInput("my-input")
    
    <div>
        <button class="btn btn-primary" @onclick="this.Reset">Reset</button>
    </div>
    
    <div class="bg-dark text-white m-2 p-2">
        <pre>Value : @_value</pre>
    </div>
    
    @code {
        private string? _value;
    
        private RenderFragment<string> CustomInput => tag => builder =>
        {
            builder.OpenElement(5, tag);
            builder.AddAttribute(6, "class", "form-control mb-3");
            builder.AddAttribute(7, "value", BindConverter.FormatValue(_value));
            builder.AddAttribute(8, "onchange", EventCallback.Factory.CreateBinder(this, __value => _value = __value, _value!));
            builder.SetUpdatesAttributeName("value");
            builder.CloseElement();
        };
    
        private void Reset()
            => _value = null;
    
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search