Aspnetcore: Add 'bind-...' for two-way binding

Created on 28 Mar 2018  路  21Comments  路  Source: dotnet/aspnetcore

Summary

We're adding a feature that replaces @bind(...) with something more first class for tooling.

Introducing bind-... for two-way binding:

<input bind="@CurrentValue" />
@functions {
    public string CurrentValue { get; set; }
}

bind-... attribute implements two-way binding to components and dom elements via a tag helper with special compile-time behavior. This means that intellisense, colorization, and completion know about bind-... and can provide contextual documentation and contextual completion.

image

note: the correct date format is actually yyyy-MM-dd. Do as I say, not as I do 馃槅

What is two-way binding?

If you're not familiar with @bind (the thing being upgraded), this does two-way binding to both components and dom elements. This is like a macro, it doesn't do anything you couldn't do normally, but it should help you out 馃憤

Example:

<input bind="@CurrentValue" />
@functions {
    public string CurrentValue { get; set; }
}

Generates something like:

<input value="@CurrentValue" onchange="@((__value) => CurrentValue = __value)/>
@functions {
    public string CurrentValue { get; set; }
}

This means than when the component first runs, the value of the input element will come from the CurrentValue property. When the user types in the textbox, the CurrentValue property will be set to the changed value. This is why we call it two-way binding.

In reality the code generation is a little more complex because bind deals with a few cases of type conversions. But in principle, bind-... will associate the current value of an expression with a value attribute, and will handle changes via a change handler attribute. We expect to invest more in the runtime support in this area in the future, including better conversion, error handler, etc.

One additional point, the expression provided to bind-... should be an LValue, meaning that it's something that can be assigned. Since this is code-generation, it works in places that the C# ref keyword doesn't work.

React can do something similar: https://reactjs.org/docs/two-way-binding-helpers.html

Use cases

DOM elements

The most obvious usage of bind-... is for input elements of various types. You're going to have lots of these 馃槅

That looks like this:

<input bind="@CurrentValue" />
@functions {
    public string CurrentValue { get; set; }
}

Or this:

<input type="checkbox" bind="@IsSelected" />
@functions {
    public bool IsSelected { get; set; }
}

For cases like this, we have a set of mappings between the structure of an input tag and the attributes that we need to set of the generated dom elements. These mappings are driven by attributes defined in code and are extensible.

Right now this set is minimal, but we plan to provide a good set of mappings out of the box for completion to lead you down the right path.

Format strings

You can also provide a format string - currently only for DateTime values.

<input type="date" bind="@StartDate" format="yyyy-MM-dd" />
@functions {
    public string StartDate { get; set; }
}

The format string will be used to convert to and from .NET values to the DOM attributes. We plan to enhance this area in the future, currently it's restricted to DateTime. If you use this with an expression that isn't a DateTime, expect some fireworks 馃巻

Components

bind-... can also enhance components by recognizing component attributes that follow a specific pattern.

That looks like:

@* in Counter.cshtml *@
<div>...html omitted for brevity...</div>
@functions {
    public int Value { get; set; } = 1;
    public Action<int> ValueChanged { get; set; }
}

@* in another file *@
<Counter bind-Value="@CurrentValue" />
@functions {
    public int CurrentValue { get; set; }
}

The code generation for components is a little plainer. Since you're calling into .NET code from .NET code, we expect the types to match.

Note that the component author doesn't have to do anything special to enable this, we detect the bindables based on the names and types of the component properties.

Bind, user-defined

If you have a use for it, you can define your own mappings for bind-... and do things like this:

// in BindAttributes.cs
[BindElement("ul", "foo", "myvalue", "myevent")]
public class BindAttributes
{
}

@* in MyComponent.cshtml *@
<ul bind-foo="@SomeExpression" />

Bind, general case

bind-... also supports a fallback case that totally flexible. You can specify bind with any value attribute name and change handler attribute name.

<ul bind-myvalue-myevent="@SomeExpression" />

This will bind the current value of @SomeExpression to myvalue and a change handler lambda to myevent.

Next steps

The first change here introduces the general concept of bind as a language feature rather than a function call. I think that this works well for the cases that we have tested with @bind, but we need to do more work in the runtime to make it really complete.

This includes:

  • support conditional attributes
  • extend the set of bind-... mappings we provide by default
  • improve support for conversions
  • improve error handling for conversion failures

One of the next things we do will be to give the same treatment to @onclick and extend the set of default mappings we provide for event handlers.

There's also a master list of improvements to the programming model for components described here: https://github.com/aspnet/Blazor/issues/1

That list doesn't have much detail is only a general outline.

area-blazor

Most helpful comment

IMHO these custom attributes (bind, format, converter, ...) should be prefixed to distinguish them from the standard HTML attributes

All 21 comments

We should also ensure that using a single state store like the FlightFinder demo does works just as well.

If you auto bind to a property then another component depends on that property to render, it does not get re-rendered because you haven't fired the StateHasChanged() event. And thats pretty hard to do without getting into ugly stuff like the below.

<input class="toggle" type="checkbox" @bind(completed) />
@functions
{
    bool completed
    {
        get { return state.Todos[Id].Completed; }
        set { state.Todos[Id].Completed = value; StateHasChanged(); }
    }
}

IMHO these custom attributes (bind, format, converter, ...) should be prefixed to distinguish them from the standard HTML attributes

@rynowak I am trying to understand if there is a way for bind to provide ''bindable" data to the compnents ChildContent?

Think of this scenario, for example:

@* Consumer is sending raw data to the component for "processing" *@
<MyStockExample bind-rawstockdata="@rawStockData" bind-processedstockdata="@processedData">
    <textarea rows="5" >
           @processedData
    </textarea>
</MyStockExample>


@functions{
    public ProcessedStockData processedData { get; set; }
    public RawStockData rawStockData { get; set; }
}

Wherein, MyStockExample is taking in RawStockData and then "processing" it and providing it as a bindable object to its child content as (ProcessedStockData) so that the consumer can then use that data without having to depend on the component author.

The question is would the above be possible with this change? If no, is this approach not the right one in context of Blazor components?

@muqeet-khan - you don't need bind for that case because it's not two-way, and not changing.

We plan to address this scenario also in the future.

@guardrex

If this requires manual work to update the DOM after a programmatic change to the property then it's not two-way binding imo. It will confuse a lot of devs coming over from other frameworks. It will also make the code harder to maintain in many cases.

All definitions I can find of two-way binding specify an immediate automatic update of both the rendered output and the stored value upon change, in both directions.

Or are there plans to also implement an auto-propagating two-way binding alternative?

bdparrish picture bdparrish on 29 Mar 2018
👍1

Why not asp-bind like aspnet core existing tag helpers for consistency reasons?

I personally prefer this new "bind-" syntax to the @Bind previously. However is there even a need for the "bind-*" syntax?

I personally think Polymer has a very nice model for bindings.

To me the following transformation doesn't feel intuitive - what does "bind" bind to?. Is it "value", or "id", or "type", etc

<input bind="@CurrentValue" />

to

<input value="@CurrentValue" onchange="@((__value) => CurrentValue = __value)/>

If something like the Polymer syntax were used though then the binding is unambiguous:

<input value="{{CurrentValue}}"/>

I also wonder if things like string formatting (from top post):

<input type="date" bind="@StartDate" format="yyyy-MM-dd" />

Could be achieved with the string interpolation syntax. With the syntax below I don't have to learn anything Blazor specific, I can just use my existing HTML and C# knowledge:

<input type="date" value="@StartDate:yyyy-MM-dd"/>

To take things a little further, the binding syntax could be augmented to signify one/two way bindings. Polymer syntax is:

One-way:

<input value="[[CurrentValue]]"/>

Two-way:

<input value="{{CurrentValue}}"/>
I'm in no way suggesting that you do exactly the same thing as Polymer, I just think there syntax has a lot of nice aspects.

The code should be moved to JavaScript or the @functions section. Leaving in the HTML will make it difficult to debug in the Devloper Tools. It also clutters the HTML.

@bdparrish - this is an example of the what the generated code looks like, it's not something we should suggest anyone write.

@rynowak

I understand that. I know that the code is simple but code needs to be separated from the presentation.

Also if the generated code looks like that, then it makes it difficult to debug those parts of the HTML.

Aurelia binding syntax is very intutive, why cant blazor use similar.

http://aurelia.io/docs/binding/basics

Auerlia data binding syntax is more descriptive and describes it's purpose.

image

http://aurelia.io/docs/binding/basics#html-and-svg-attributes

Closing this issue as this feature was introduced with 4407de18baac1ad715549b26d292daa1d2fc3658

Thanks for the feedback and discussion on this topic. We chose this style because it feels very Razor and we're already working with something established (server-side Razor).

Excuse a silly question, but what is the role of the Action in the Counter example? What exactly is required to bind to values in a component?

Without Action you have only one way data binding. Also take into account that information in this post is a little outdated. Please read this in the doc:
https://blazor.net/docs/components/index.html#component-parameters
and example you are looking for is below Components attributes subheading.

Is there a list somewhere that shows the different type's of bindings for different types of controls?

@McHeff Take a look at https://docs.microsoft.com/en-us/aspnet/core/razor-components/components?view=aspnetcore-3.0#data-binding and see if it answers your questions.

@McHeff Take a look at https://docs.microsoft.com/en-us/aspnet/core/razor-components/components?view=aspnetcore-3.0#data-binding and see if it answers your questions.

Thanks @danroth27, I've already looked at that page, what I was looking for is like a list of all available types of bindings... Working with Blazor is fun, but I was looking for different types of bindings across different types of controls.

Example. checkboxes have "bind" as well as "bind-value-changed" I believe, so I was wondering what other types there are as intelisense doesn't show them...

Anyways, thanks for the reply man!

Oh, and if you're interested Dan, I've made a discord server for Blazor: https://discord.gg/Xg9ja5s

I was going crazy with this! the book was saying we should use like this:
<input type="text" bind="@variable" />
but the proper usage should be:
<input type="number" @bind="@variable" />

kudos sipi41. I got "Missing attribute name" @variable but still works.

Was this page helpful?
0 / 5 - 0 ratings