Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to bind to element from collection/list in Blazor?

I want to bind values to elements from my list in a loop but I cannot find the good solution.

<EditForm Model="@dailyReport" OnValidSubmit="@SubmitDailyReport">
        <p>
            <label>Count: </label>
            <InputNumber min="1" id="numberOfEffects" @bind-Value="numberOfEffects" />
        </p>
        @{
            for (int i = 1; i <= numberOfEffects; i++)
            {
                if (effects.Count < i)
                {
                    effects.Add("");
                }

                if (effects.Count > i)
                {
                    effects.RemoveAt(effects.Count - 1);
                }

                string concatId = "effect" + i.ToString();

                <p>
                    <label for="@concatId">@i</label>
                    <InputText id="@concatId" @bind-Value="effects.ElementAt(i-1)" />
                </p>
            }
         }
//rest code
 </EditForm>

When I am trying to bind-Value to element from a list I am getting an error:

  1. error CS0131: The left-hand side of an assignment must be a variable, property or indexer error
  2. error CS1662: Cannot convert lambda expression to intended delegate type because some of the return types in the block are not implicitly convertible to the delegate return type

I wonder if there is any possible way to bind data to collection. I need it because my Model has a property of type List. In my input form I want to allow adding as many strings as will be needed by the user.

edit:

@code
{
    private DailyReport dailyReport = new DailyReport();
    private List<string> effects = new List<string>();
}

I also tried to do it in foreach loop but another error is showed: Cannot assign to 'effect' because it is a 'foreach iteration variable

foreach (var effect in effects)
            {
                index++;
                Console.WriteLine(index);

                string concatId = "effect" + index.ToString();

                <p>
                    <label for="@concatId">@index</label>
                    <InputText id="@concatId" @bind-Value="effect" />
                </p>
            }
like image 738
scarybook Avatar asked Feb 26 '20 14:02

scarybook


1 Answers

The binding cannot work: when the value of an input text changes, you want to modify the list, not the element itself.

Instead, you have to "split" what the binding does in the two directions:

  • set the value of the input field based on the value of the model
  • set the model when the value of the input field changes

The code below shows how to solve the main problem, then you have to adapt it to your needs.

@foreach (var element in effects.Select((e, i) => new { Effect = e, Index = i}))
{
    <p>
        <label for="@($"effect{element.Index}")">@element.Index</label>
        <input id="@($"effect{element.Index}")" value="@element.Effect"
               @onchange="@(e => effects[element.Index] = e.Value.ToString())" />
    </p>
}

@code {

    private List<string> effects = new List<string>() { "a", "b", "c" };

}

The first line does the trick: it converts the list to an enumerable in which each element is a new object that encapsulates both the index in the effects list and the value. You need the index mainly because of the @onchange event, which must know where in the original list the value should be updated.

If you prefer you can use a for loop:

@for (int i = 0; i < effects.Count; i++)
{
    // see https://stackoverflow.com/a/56426146/323447
    var iCopy = i;

    <p>
        <label for="@($"effect{i}")">@i</label>
        <input id="@($"effect{i}")" value="@effects[i]"
               @onchange="@(e => effects[iCopy] = e.Value.ToString())" />
    </p>
}

@code {

    private List<string> effects = new List<string>() { "a", "b", "c" };

}
like image 105
Paolo Fulgoni Avatar answered Oct 11 '22 02:10

Paolo Fulgoni