Aug 15

Written by: Michael Washington
8/15/2019 2:07 PM  RssIcon

image

Three things that you will usually find yourself using on every Blazor page, Binding, Events, and Parameters, will be covered here. To demonstrate these, we will build a series of pages including a few that will allow a user to build and edit a list of To Do items.

 

Create The Page

image

Create a new Server Side Blazor project called BindingEventsParameters.

When the project opens in Visual Studio, right-click on the Pages folder and select Add then New Item…

 

image

Select the Razor Component template, name the page OneWayBinding.razor, and click the Add button.

 

image

The page will display.

Add the following line to the top of the page, to implement routing for the control:

 

@page "/OneWayBinding"

 

The final code should look like this:

 

@page "/OneWayBinding"
<h3>OneWayBinding</h3>
@code {
}

 

image

In the Shared folder, open the NavMenu.razor page.

Add the following to the menu control:

 

        <li class="nav-item px-3">
            <NavLink class="nav-link" href="OneWayBinding">
                <span class="oi oi-list-rich" aria-hidden="true"></span> OneWay Binding
            </NavLink>
        </li>

 

image

Hit F5 to run the project…

 

image

When the application opens in the web browser, you can click the OneWay Binding link in the navigation, and the OneWayBinding.razor control will display.

In Visual Studio, select Shift+F5 to stop the application.

 

One Way Binding

Binding allows you to set and alter a value in one part of your code, and have it display in another part of your code.

For example, you can create a property in the processing logic of a control, and display the value of that property in the User Interface (UI) markup of the control.

This is called binding and it is achieved using the @bind attribute.

 

Alter the code of the OneWayBinding.razor control to the following:

 

@page "/OneWayBinding"
<h3>@Title</h3>
@code {
    string Title =
        "The name of this page is: One Way Binding";
}

 

image

When we run the application, we see that the value we set for the Title variable displays in the UI.

 

image

Setting a value for a variable or a property, and simply consuming it is called one way binding.

 

Two Way Binding

image

To demonstrate two way binding, create a new page called TwoWayBinding.razor, and add a link to it in the NavMenu.razor control.

Use the following code for the TwoWayBinding.razor control:

 

@page "/TwoWayBinding"
<h3>Two Way Binding</h3>
<p>Slider Value: <b>@SliderValue</b></p>
@code {
    int SliderValue = 0;
}

 

image

When we run the project, and navigate to the control, we see the SliderValue is set to zero using one way binding.

Stop the project and add the following code to the UI markup:

 

<input type="range" step="1" 
       @bind-value="SliderValue"
       @bind-value:event="oninput" />

 

This adds a slider control to the page that is bound to the SliderValue variable. However, this time we are using the @bind-value attribute. Using this allows us to specify an an event parameter for the @bind-value attribute.

In this case we have selected the oninput parameter that will update the SliderValue variable with the current slider value each time the slider value changes.

If we did not use the oninput parameter, the SliderValue variable would not update until we clicked away from the slider control.

 

image

When we run the project, we can move the slider bound to the SliderValue variable, and the SliderValue variable is updated immediately and displayed.

 

image

When we manipulate a variable or property, through a UI control, rather than just consuming it, we are implementing two way binding, because the the consumer is updating the source.

 

Create a To Do Page

Let’s take what we learned so far about binding and create the classic to do application.

image

Create a new folder called Classes and add a class called TaskItem.cs using the following code:

 

using System;
namespace BindingEventsParameters
{
    public class TaskItem
    {
        public string TaskDescription { get; set; }
        public bool IsComplete { get; set; }
    }
}

 

image

Next, add a new page called ToDoPage.razor to the Pages folder (also add a link to the control in the NavMenu.razor page).

Change all the code in the ToDoPage.razor control to the following:

 

@page "/ToDoPage"
<h3>ToDo Page</h3>
<br />
<ul class="list-group">
    @foreach (var Task in Tasks)
    {
        <!-- Use @key to ensure correct Blazor -->
        <!-- diffing algorithm behavior when binding a list -->
        <li @key="Task"
            class="list-group-item form-check form-check-inline">
            <input type="checkbox"
                   class="form-check-input"
                   @bind-value="Task.IsComplete" />
            <label class="form-check-label"
                   for="inlineCheckbox1">
                @Task.TaskDescription
            </label>
        </li>
    }
</ul>
@code {
    // Collection to hold all the Tasks
    private List<TaskItem> Tasks = new List<TaskItem>();
    // This method will run when the control is loaded
    protected override void OnInitialized()
    {
        // Add a Task
        Tasks.Add(new TaskItem()
        {
            TaskDescription = "Task One",
            IsComplete = false
        });
    }
}

 

image

When we run the application we see that a Task will be displayed using one way binding.

Now, add the following UI markup:

 

<br />
<div class="container">
    <div class="row">
        <div class="col">
            <input class="form-control"
                   placeholder="Add a Task"
                   @bind-value="newTaskDescription" />
        </div>
        <div class="col">
            <button class="btn btn-primary"
                    type="button"
                    @onclick="AddTask">
                Add Task
            </button>
        </div>
    </div>
</div>

 

Add the following code to implement the AddTask method:

 

    // Property to hold the description of a new Task
    // The textbox is bound to this property
    private string newTaskDescription;
    private void AddTask()
    {
        if (!string.IsNullOrWhiteSpace(newTaskDescription))
        {
            // Create a new Task
            // using the current value of the 
            // newTaskDescription property
            var NewTask = new TaskItem();
            NewTask.TaskDescription = newTaskDescription;
            NewTask.IsComplete = false;
            // Add the new Task to the collection
            Tasks.Add(NewTask);
            // Clear the newTaskDescription value 
            // so the text box will now be empty
            newTaskDescription = string.Empty;
        }
    }

 

image

When we run the application, we can enter a new Task and click the Add Task button.

 

image

The Task is then added to the Tasks collection, and automatically displayed in the list.

 

The following diagram shows how the binding works:

image

We can also add a Delete button to list that will pass the current Task to a RemoveTask method:

 

        <button type="button" class="btn btn-link"
                @onclick="(() => RemoveTask(Task))">
            [Delete]
        </button>

 

We then add code to implement the RemoveTask method:

 

    private void RemoveTask(TaskItem paramTaskItem)
    {
        // Remove the Task from the collection
        Tasks.Remove(paramTaskItem);
    }

 

image

When we run the application, we can click the [Delete] button next to a Task

 

image

…and the Task will be removed for the collection, and the list will be automatically updated.

Finally, we can add the following UI markup to the page:

 

<p>
    Tasks: <b>@(Tasks.Count())</b>
    Completed:
    <b>@(Tasks.Where(x => x.IsComplete == true).Count())</b>
</p>

 

image

This will automatically display a count of the Tasks, and track the number of Tasks marked completed.

 

image

This demonstrates how bindings to properties are automatically updated in the UI whenever Blazor detects there are changes to the values.

 

Parameters

Parameters allow one control to set the value of properties on another control.

image

Create a new page called ToDoComponent.razor using the following code:

 

<ul class="list-group">
    @foreach (var Task in Tasks)
    {
        <li class="list-group-item form-check form-check-inline">
            <label class="form-check-label"
                   for="inlineCheckbox1">
                @Task.TaskDescription
            </label>
        </li>
    }
</ul>
@code {
    // Collection to hold the Tasks
    // passed in by the parent component
    [Parameter] public List<TaskItem> Tasks { get; set; }   
}

 

Note that we did not define a @page attribute because we do not need routing to this component. This component will be directly consumed by a parent component.

Change the UI markup of the ToDo.razor page to the following:

 

@page "/ToDoPage"
<h3>ToDo Page</h3>
<br />
<ToDoComponent Tasks="Tasks" />

 

 

image

When we run the application we see that the Tasks collection, from the parent component (ToDo.razor), is passed as a parameter and displayed in the list in the child component (ToDoComponent.razor).

 

image

This is essentially binding as we explored earlier, however it is one way binding.

 

Calling A Method On A Child Component

While parameters allow us to pass values from a parent control to a child control, it will not allow us to invoke a method on a child control.

One method that will allow this, is to use the @ref attribute.

 

Add the following code to the ToDoComponent.razor page:

 

    // This method will be called directly by the parent 
    // component
    public void AddNewTask(TaskItem paramTaskItem)
    {
        // Add the new Task to the collection
        Tasks.Add(paramTaskItem);
    }

 

Add the following property to the ToDoPage.razor control:

 

    // This will hold a reference to the ToDoComponent
    private ToDoComponent ToDoComponentControl;

 

Change the AddTask method to the following:

 

    private void AddTask()
    {
        if (!string.IsNullOrWhiteSpace(newTaskDescription))
        {
            // Create a new Task
            // using the current value of the
            // newTaskDescription property
            var NewTask = new TaskItem();
            NewTask.TaskDescription = newTaskDescription;
            NewTask.IsComplete = false;
            // Add the new Task to the collection
            // by calling a method on the child component
            ToDoComponentControl.AddNewTask(NewTask);
            // Clear the newTaskDescription value
            // so the text box will now be empty
            newTaskDescription = string.Empty;
        }
    }

 

Next, implement the @ref attribute on the ToDoComponent in the UI markup, so it now reads:

 

    <ToDoComponent Tasks="Tasks" 
                   @ref:suppressField 
                   @ref="ToDoComponentControl" />

 

Finally, add an input control and a button to add new Tasks:

 

<br />
<div class="container">
    <div class="row">
        <div class="col">
            <input class="form-control"
                   placeholder="Add a Task"
                   @bind-value="newTaskDescription" />
        </div>
        <div class="col">
            <button class="btn btn-primary"
                    type="button"
                    @onclick="AddTask">
                Add Task
            </button>
        </div>
    </div>
</div>

 

image

When we run the application we are able to add Tasks from the parent control by calling the method on the child control.

 

image

The diagram above illustrates how a reference is made on the ToDoComponent control, using the @ref attribute, and how that reference is then used to call the AddNewTask method on that control.

 

Events

We have explored various methods to communicate from a parent component to a child component. Events, using EventCallback, provides a method that allows a child component to communicate with a parent component.

 

Add the following code to ToDoComponent.razor:

 

    // RemoveTaskChanged is an EventCallback that will
    // notify the parent component when an item is to be removed
    // passing the item to be removed
    [Parameter] public EventCallback<TaskItem> RemoveTaskChanged { get; set; }
    private async Task RemoveTask(TaskItem paramTaskItem)
    {
        // Notify parent component to
        // Remove the Task from the collection
        await RemoveTaskChanged.InvokeAsync(paramTaskItem);
    }

 

Also, add a delete button (that will appear next to each list item), that will call the RemoveTask method (passing the current Task):

 

            <button type="button" class="btn btn-link"
                    @onclick="(() => RemoveTask(Task))">
                [Delete]
            </button>

 

When we look at ToDoPage.razor we see we already have this method to remove a Task from the collection:

 

    private void RemoveTask(TaskItem paramTaskItem)
    {
        // Remove the Task from the collection
        Tasks.Remove(paramTaskItem);
    }

 

Finally, we need to update the ToDoComponent tag to indicate that the RemoveTask method is to be invoked when the RemovedTaskChanged event handler is invoked in the child control (ToDoComponent.razor):

 

    <ToDoComponent Tasks="Tasks" @ref:suppressField   
                   @ref="ToDoComponentControl" 
                   RemoveTaskChanged="RemoveTask" />

 

image

When we run the application, we can now click the [Delete] button…

 

image

… to remove a Task from the list.

 

image

The diagram above shows how the child component raises the event by calling InvokeAsync passing the currently selected Task as a parameter.

 

Cascading Parameter

The final example will demonstrate cascading parameters. A cascading parameter is different from a normal parameter in that the value of the cascading parameter will pass to all child controls that declare the parameter.

 

Add the following code to ToDoPage.razor:

 

    // SelectedColor will be the cascading parameter
    // Set the color to Green
    protected string SelectedColor { get; set; } = "Green";
    // Create a collection of colors that will be bound to a dropdown
    List<string> Options = new List<string>() { "Green", "Red", "Blue" };

 

Add the following UI markup to the page, to display the currently selected color and a dropdown to allow it to be changed:

 

<label for="Summary">Theme Color: </label>
<select class="form-control"
        @bind="@SelectedColor">
    @foreach (var option in Options)
    {
        <option value="@option">
            @option
        </option>
    }
</select>
<br />

 

Finally, surround the ToDoComponent tag with the CascadingValue tag, to name the cascading parameter ThemeColor, and to pass the SelectedColor variable as its value:

 

<CascadingValue Value=SelectedColor Name="ThemeColor">
    <ToDoComponent Tasks="Tasks"        
                   @ref="ToDoComponentControl" @ref:suppressField 
                   RemoveTaskChanged="RemoveTask" />
</CascadingValue>

 

In ToDoComponent.razor add the following code:

 

    // Declare the ThemeColor CascadingParameter
    // from the ancestor control as ThemeColorParameter 
    [CascadingParameter(Name = "ThemeColor")]
    protected string ThemeColorParameter { get; set; }

 

Next, alter the label in the UI markup to the following, to allow the color to be set by the value of ThemeColorParameter:

 

        <label class="form-check-label"
               for="inlineCheckbox1"
               style="color:@ThemeColorParameter">

 

 

image

We can run the application…

 

image

change the value of the cascading parameter

 

image

… and the value is immediately updated on all child components that declare and implement the parameter.

 

image

The diagram above shows how the cascading parameter is declared and consumed.

 

Links

Blazor.net

 

Download

The project is available at http://lightswitchhelpwebsite.com/Downloads.aspx

You must have Visual Studio 2019 Preview Edition (or higher) installed to run the code.

Tags: Blazor
Categories:

3 comment(s) so far...


Gravatar

Re: Blazor Binding, Events and Parameters

Hi,

Excellent article! However, there is a bug that may not be yours: "@bind-value" doesn't work with checkbox. To reproduce the bug, in your section "Two way binding",
step 1. Add couple new tasks. Check the 1st one as completed. So now we have 3 tasks, with one completed.
step 2. Add couple more.
step 3. Delete the selected task. You will see that the selected task is successfully deleted. However, the next task will bump up the list but its checkbox now becomes completed, even though we have never checked this checkbox.

To fix this bug, we can change the code from

to


Don't know if you know anyone that is in the "@bind-value" team.

By Jeff Liu on   8/10/2019 8:32 PM
Gravatar

Re: Blazor Binding, Events and Parameters

@Jeff Liu - Your code suggestion did not come through, but, I think I need to try this to fix the issue...: "Use @key to control the preservation of elements and components" https://docs.microsoft.com/en-us/aspnet/core/blazor/components?view=aspnetcore-3.0#use-key-to-control-the-preservation-of-elements-and-components

By Michael Washington on   8/10/2019 8:37 PM
Gravatar

Re: Blazor Binding, Events and Parameters

@Jeff Liu - Thanks for bringing this to my attention. I added @key to the article and the code download and that appears to have fixed the issue.

By Michael Washington on   8/11/2019 6:11 AM

Your name:
Gravatar Preview
Your email:
(Optional) Email used only to show Gravatar.
Your website:
Title:
Comment:
Security Code
CAPTCHA image
Enter the code shown above in the box below
Add Comment   Cancel 
Microsoft Visual Studio is a registered trademark of Microsoft Corporation / LightSwitch is a registered trademark of Microsoft Corporation