Aspnetcore: Support chained 'bind' on components

Created on 18 Apr 2018  路  16Comments  路  Source: dotnet/aspnetcore

I must be doing something wrong.

Made this component:

<div>
    <input bind="@myValue" />
</div>

@functions
{
    public string myValue { get; set; }
    public Action<string> myValueChanged { get; set; }
}

And use this in a page like this:

```


test: @Test

@functions {

public string Test { get; set; }

}
````

Now I expect that typing in either input on this page would result in the same values in both inputs. But this seems to work only one-way: from the input to the control and not the other way around.

This is supposed to work right? What am I doing wrong?

Note (from @SteveSandersonMS)

I expect fixing this will also fix #1490. Any proposed fix for this issue should also be checked to verify it accounts for the scenarios raised at #1490, and if it doesn't we'll reopen #1490 as a separate issue.

area-blazor bug

Most helpful comment

@conficient I'll optimistically put this in the milestone and we'll see if we can get to it.

All 16 comments

I believe you need to call the myValueChanged action in your myInput component when myValue changes. Presumably you'd want to do that when the input in the myInput component changes. I don't think you can use bind then on the input because I think you can only have one event handler for onchange (bind adds an onchange event handler). But you can set value and implement onchange yourself like this:

<div>
    <input value="@myValue" onchange="@InputChanged" />
</div>

@functions
{
    public string myValue { get; set; }
    public Action<string> myValueChanged { get; set; }

    void InputChanged(UIChangeEventArgs e)
    {
        myValue = (string)e.Value;
        myValueChanged(myValue);
        Console.WriteLine($"InputChanged: {myValue}");
    }
}

However, the myValueChanged event doesn't seem to cause the parent component to render (@rynowak is this a bug?), so currently I think you have to do this in the parent component:

<input bind="@Test" /><br />

<myInput myValue="@Test" myValueChanged="@MyValueChanged" /><br />

test: @Test

@functions {
    public string Test { get; set; }

    void MyValueChanged(string myValue)
    {
        Console.WriteLine($"MyValueChanged: {myValue}");
        Test = myValue;
        StateHasChanged();
    }
}

Ah, this helps. I now see that the documentation nowhere mentions that databinding is two-way. I assumed it was going to work this way, I guess because Aurelia and Angular do it this way.

Are you planning on making it work this way? I'll opt for that, it looks cumbersome as it is right now.

@rynowak To make the multiple-connected-bindings scenario work correctly, I propose we amend the codegen output for bind so that:

  1. After the change handler writes the new property value, we invoke StateHasChanged. It doesn't matter if this means there are cases where it gets invoked twice synchronously in succession, because the BlazorComponent base class no-ops additional calls if there's already a queued render. This will fix the problem noted by Dan above.
  2. If you're binding to MyProp, and your component also declares a property Action MyPropChanged { get; }, then after writing to MyProp we should invoke MyPropChanged. This will fix the original issue reported here.

What do you think? Is the second item there technically reasonable to implement - do we know whether a certain-named other property exists at the right point in the compilation process? I'm guessing that the two-phase compile makes this possible, though I don't know whether it would be convenient to do still.

@floreseken Data binding is two way, so I'm not sure what you mean by that comment. There are a couple of usability issues with it, for which I'm proposing the two fixes above.

@SteveSandersonMS - yes, both of these sounds like good enhancements and very doable.

If you're binding to MyProp, and your component also declares a property Action MyPropChanged { get; }, then after writing to MyProp we should invoke MyPropChanged. This will fix the original issue reported here.

FYI If you're in the case where you don't declare MyPropChanged we don't provide automatic completion for bind-MyProp. You can still write it yourself, but we don't add it to the completion list. I still think this is the right behavior, let me know if you have feedback

@rynowak Sounds right!

If I have something like bind-myProp-myPropChanged="somevariable" and not actually declare myPropChanged will it be invoked?

No, even if you do declare it, it doesn't get invoked magically.

But Steve is proposing a change that will.

@SteveSandersonMS could this please be part of the 0.4 release? It is holding me back from creating any component with inputs in them.

I've written a blogpost on a workaround which involves less code if you have a lot of components.

http://flores.eken.nl/blazor-custom-component-with-2-way-databinding/

Maybe something for 0.6.0? which could potentially speed up the development of component libs.

I'd just run into this bug trying to create bootstrap form components. It looks like anything in a RenderFragment doesn't seem to work, even with StateHasChanged being fired.

My test repo: https://github.com/conficient/BlazorBug01

There are two different problems here: first, a bind inside a parent RenderFragement only binds when it's accessed, and a bind-{propertyName} does not seem to fire onchange event at all.

Suggestions welcome.

NOTE: When we fix this, we should also check the fix handles the scenarios raised at #1490, and if it doesn't we'll reopen #1490 as a separate issue.

@danroth27 could I make a request to include this issue in 0.7.0, or failing that the next release? It appears this problem exists in server-side as well so would affect Razor Components going forward. Plus it will make creating re-usable components much easier (e.g. Bootstrap 4 layouts as in my repo are a classic example)

@conficient I'll optimistically put this in the milestone and we'll see if we can get to it.

I created a fiddle that shows I believe the same issue:

https://blazorfiddle.com/s/861k07nk

If you change the value in the calendar, the value is updated in the component, but not in the parent.
If you then change the value in the textbox, the value for the calendar is changed to what was originally bound to it.

I hope this helps.

We expect to handle this as part of #6351

Was this page helpful?
0 / 5 - 0 ratings

Related issues

FourLeafClover picture FourLeafClover  路  3Comments

fayezmm picture fayezmm  路  3Comments

farhadibehnam picture farhadibehnam  路  3Comments

aurokk picture aurokk  路  3Comments

BrennanConroy picture BrennanConroy  路  3Comments