Search code examples
bindingblazor.net-7.0

What is the difference between bind:after and bind:set in Blazor 7?


Recently, in Blazor 7, a feature has been added to make it easier to bind and call the method based on changes in the bound expression.

In .NET 7, you can now easily run async logic after a binding event has completed using the new @bind:after modifier:

    <input @bind="searchText" @bind:after="PerformSearch" />
@code {
    string searchText = "";

    async Task PerformSearch()
    {
        // Do something async with searchText
    }
}

In this example, the PerformSearch async method runs automatically after any changes to the search text are detected.

Another method has been added too. The @bind:get and @bind:set modifiers are always used together. The @bind:get modifier specifies the value to bind to, and the @bind:set modifier specifies a callback that's called when the value changes.

The questions are:

What is the difference between @bind:after="PerformSearch" and @bind:set="PerformSearch"? Both of these seem to call the PerformSearch after the searchText is changed.

Where is the use of each?


Solution

  • What is the difference between @bind:after="PerformSearch" and @bind:set="PerformSearch"?

    1. You should only use @bind:after="PerformSearch" with @bind="searchText",in which case the bind will set the value of searchText, so you shouldn't also try and set it in PerformSearch.

    2. If you use @bind:set="PerformSearch" then you must set the value of searchText in PerformSearch, and use @bind:get="searchText".

    Where is the use of each?

    The MS Docs article I think gives a good guide. It all depends on your knowledge level on components.

    It's important to understand two points:

    1. This is Razor syntax, not C#.
    2. It's just syntactic sugar: high level functionality, shorthand Razor directives to encapsulate existing functionality.

    Also note:

    Here's my demo page for this answer.

    @page "/"
    
    <PageTitle>Index</PageTitle>
    
    <input class="form-control mb-3" type="text" @bind:get="this.Value" @bind:set="ValueSetter" @bind:event="oninput" />
    <input class="form-control mb-3" type="text" @bind:get="this.Value" @bind:after="ValueSetter" />
    <input class="form-control mb-3" type="text" @bind="this.Value" @bind:after="DoSearch" @bind:event="oninput"/>
    
    <div class="alert alert-info m-2 p-2">
        @Value
    </div>
    
    <div class="alert alert-primary m-2 p-2">
        @message
    </div>
    
    @code {
        private string? Value; 
        private string message = "Not Set";
    
        private async Task DoSearch()
        {
            await Task.Delay(1000);
            message= $"Set at {DateTime.Now.ToLongTimeString()}";
        }
    
        private void ValueSetter(string __value)
            => this.Value = __value;
    
        private Task SearchSetter(string __value)
        {
            this.searchText = __value;
            return DoSearch();
        }
    }
    

    Let's look at the actual C# code the Razor compiler builds.

    This is the code snippet when just using bind:set=this.ValueSetter:

    __builder.AddAttribute(8, "oninput", EventCallback.Factory.CreateBinder(
        this, 
        CompilerServices.RuntimeHelpers.CreateInferredBindSetter(
            callback: this.ValueSetter, 
            value: this.Value
            ),
        this.Value));
    

    This simply calls the setter delegate assigned to set.

    This is the code snippet when using :bind=this.Value and @bind:after=DoSearch:

    __builder.AddAttribute(14, "oninput", EventCallback.Factory.CreateBinder(
        this, CompilerServices.RuntimeHelpers.CreateInferredBindSetter(
            callback: __value => { 
                this.Value = __value; 
                return RuntimeHelpers.InvokeAsynchronousDelegate(callback: DoSearch); 
            }, 
            value: this.Value), 
            this.Value));
    

    It's a little more complicated. The compiler builds the equivalent to this:

    Task AnonymousMethod(string __value)
    {
        this.Value = __value;
        return DoSearch()
    }
    

    A Note on Development Environment Errors

    Depending on your development environment, you will get errors with certain combinations. Some of which at the moment appear to be misleading or totally wrong. They will be fixed shortly.

    Quote from Dan Roth: Hi folks. The VS fix for this just missed the window for 17.4.4 but should be addressed in the next VS patch update in February. We apologize for the wait and thank you for your patience!

    In Visual Studio.

    This is a correct error:

    <InputText class="form-control" @bind-Value:get="this.searchText" @bind-Value:set="this.SetSearchText" @bind-Value:after="DoSearch" />
    
    Severity    Code    Description Project File    Line    Suppression State
    Error (active)  RZ10019 Attribute 'bind-Value:after' can not be used with 'bind-Value:set'. Invoke the code in 'bind-Value:after' inside 'bind-Value:set' instead.
    

    While this is bull!

    <input class="form-control mb-3" type="text" @bind:get="this.Value" @bind:set="ValueSetter" @bind:event="oninput" />
    

    And while it gives this error compiles and runs!

    Severity    Code    Description Project File    Line    Suppression State
    Error (active)  CS1503  Argument 3: cannot convert from 'Microsoft.AspNetCore.Components.EventCallback<string>' to 'System.Action<string?>' 
    

    And this line:

    <input class="form-control mb-3" type="text" @bind:get="this.Value" @bind:after="ValueSetter" />
    

    Compiles but is obviously also total bull.

    __builder.AddMarkupContent(9, "\r\n\r\n<input class=\"form-control mb-3\" type=\"text\" @bind:get=\"this.Value\" @bind:after=\"ValueSetter\">\r\n\r\n");
    

    enter image description here