Blazor Component TwoWay Databinding

2019-07-27 11:59发布

问题:

I've made a Blazor component this way:

<input bind-value-oninput="Title" />


@functions {

    [Parameter] string Title { get; set; }

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

Parent:

@page "/fetchdata"

Parent:<input bind-value-oninput="Title" /> - Child:<Comp bind-Title="Title"></Comp>



@functions {
    string Title;

}

as expected editing the parent input propagate it's value to the component but not viceversa, is that by design right?

I achieved two-way behaviour with this edit:

<input bind-value-oninput="Title" />


@functions {
    string _Title { get; set; }
    [Parameter] string Title 

    {
        get { return _Title; }
        set
        {
            Console.WriteLine($"{value}->{_Title}");



            if (_Title != value)
            {
                _Title = value;
                TitleChanged.InvokeAsync(value);
            }


        }
    }

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

Is this the best practice to achieve two-way data binding? Then TitleChanged must be called manually, right? Is it like PropertyChanged of WPF?

Without

     if (_Title != value)

the code goes in loop, someone could explain why?

回答1:

This is a two-way data binding between components. This behavior (or limitation) is by design. What you do, is the correct way to solve it, and I may venture to say, a best practice, at least according Steve Anderson, others may suggest different methods, such as a service class.

Some explanation of what is going on: When you apply a change to the parent component, the change event is triggered, the state of the component has changed, and the component should be re-rendered. To re-render the parent component, the StateHasChanged method is called by Blazor automatically. But when a state is changed on the child component, the parent component is ignorant of this, and we should notify it of this. Again, using event call backs for this is a good practice, to my mind...

Hope this helps....

Edit....

In general the parameter flow goes downwards, i.e., from parent to child, not in the other direction, because the rendering flow goes in that direction. That's why there isn't a way to pass parameters upstream (e.g., to a layout), because then there's be no single defined render order.

SteveSanderson

Marco: What I don't understand is why calling StateHasChanged on the parent causes the data to flow from the parent to the child and not the opposite.

First off, you don't manually call the StateHasChanged method. It is automatically called whenever the input event is triggered; that is after each keyboard key is pressed. And it is called to rerender the Component(parent). ( Note: It is tempting to think that the input tag is an HTML input tag element, but it is not so.)

As for why not the opposite: I think the quotation by SteveSanderson made it very clear.

When you want to pass a value (This is actually a parameter, but not an Attribute property) to the Parent Component, you need to use a different mechanism such as events (recommended by SteveSanderson; and work the same like in Angular). Below is sample code how it is performed. Important: When the state of the Parent Component is being changed from a child component, we should let the Parent Component to know that its state has changed and that it should rerender, by manually calling the StateHasChanged method. This is true when using the Action delegate, though I think that the EventCallback delegate is supposed to call StateHasChanged automatically.

Component A.cshtml

// Define a method in the parent component which will be called 
// from the child component when the user tap the button residing 
// in the child component. This method has a string parameter passed
// from the child component
public void GetValueFromChild(string value)
 {
        // Do somethig with value
  } 

Component B.cshtml

// When the user click the button the method GetValueFromChild
// defined on the parent component is called

<button class="btn" onclick=@(() => OnAddValue("some string value"))>Add</button>

    @functions
{
    // Define an Action delegate property which stores a reference
    // to A.GetValueFromChild
    // Parameters
    [Parameter] Action<string> OnAddValue{ get; set; }
}

A.cshtml

// Here (in the parent component) you place the child component and
// set its OnAddValue to the name of the method to be called
<B OnAddValue = "GetValueFromChild"></B> 

Please mark my answer as accepted if it helped you out Hope this helps...