简体   繁体   中英

Blazor - Two-way binding on a Collection

Is it possible to Two-way bind an collection in Blazor child component?

TLDR ; A property of type list in an object is set to null when the ValueChanged EventCallBack handler is executed, and this throws NullReferenceException s because its set to null in the pages.

I have the following example:

    public class Person
    {
        public string FullName { get; set; }
        public List<string> NickNames { get; set; } = new List<string>();
    }

The main page: Index.razor


<h1>@Mario.FullName - @string.join('-', Mario.NickNames)</h1>
<NicknamesListComponent @bind-Nicknames="Mario.NickNames" />

@code{
    public Person Mario { get; set; } = 
        new Person() { FullName = "Mario", NickNames = new List<string> {"Super", "Mama" , "Mia"} }

}

The child component: NicknamesListComponent.razor


<ul>
    @for(int i = 0; i < Nicknames.Count; i++)
    {
      @var index = i;
      <li>
         @Nicknames[index]
         <a @onclick="() => RemoveNickname(index)">Remove</a>
      </li>
    }
</ul>

@code {
    [Parameter]
    public List<string> Nicknames { get; set; }

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

    public async Task RemoveNickname(int index)
    {
        Nicknames.RemoveAt(index);

        //////////////////////////////////////////////////////////
        // When executing NicknamesChanged. the User.Nicknames property is completly cleared and 
        // set to NULL... this throws exceptions everywhere in the index.blazor
        await NicknamesChanged.InvokeAsync();
    }
}

If I remove the NicknamesChanged , then the list with the Delete button works... but this time the parent is not notified of changes and the nicknames stay the same...

The null is because you are calling NicknamesChanged.InvokeAsync() with void.

For two way binding to work:

    await NicknamesChanged.InvokeAsync(Nicknames);

Side note: I am assuming it is a typo but string.Join(...) not string.join(...)

If you fix those two issues your code will work. However...

A better way by avoiding the for loop issue:

<ul>
    @foreach(var nickname in Nicknames)
    {
      <li @key=nickname>
         @nickname
         <a @onclick="() => RemoveNickname(nickname)">Remove</a>
      </li>
    }
</ul>

@code {
    [Parameter]
    public List<string> Nicknames { get; set; }

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

    public async Task RemoveNickname(string nickname)
    {
        Nicknames.Remove(nickname);
        await NicknamesChanged.InvokeAsync(Nicknames);
    }
}

The @key should be used as you could remove the wrong nickname.

Repl

you have to return the NickNames on NicknamesChanged, and the recommendation is don't change the NickNames Parameter. use local variable instead.

the updated code is:

<ul>
    @for (int i = 0; i < Nicknames.Count; i++)
    {
        var index = i;
        <li>
            @Nicknames[index]
            <a @onclick="() => RemoveNickname(index)">Remove</a>
        </li>
    }
</ul>
@code {
    [Parameter]
    public List<string> Nicknames { get; set; }

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

    public async Task RemoveNickname(int index)
    {
        Nicknames.RemoveAt(index);

        await NicknamesChanged.InvokeAsync(Nicknames);
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM