Data binding is one of the fundamental concepts in any modern web framework, and Blazor provides powerful and flexible binding mechanisms. In this comprehensive guide, we'll explore one-way data binding in Blazor – understanding what it is, how it works, and when to use it effectively.
What is One-Way Data Binding?
One-way data binding is a data flow mechanism where information travels in a single direction – typically from the component's code (C#) to the UI (HTML), or from a parent component to a child component. When the source data changes, the UI updates automatically, but changes in the UI do not automatically update the source data.
The Data Flow
┌─────────────────────────────────────────────────────────────┐
│ ONE-WAY DATA BINDING │
├─────────────────────────────────────────────────────────────┤
│ │
│ C# Code ──────────────────────────────────► HTML/UI │
│ (Source) Data Flows (Target) │
│ ──────────► │
│ │
│ • Source changes → UI updates automatically │
│ • UI changes → Source does NOT update automatically │
│ │
└─────────────────────────────────────────────────────────────┘
Key Characteristics
- Unidirectional Flow: Data moves in one direction only
- Read-Only Display: UI displays data but cannot modify the source directly
- Explicit Updates: To update the source, you need explicit event handlers
- Predictable: Easier to reason about and debug
- Performance: Generally more performant than two-way binding
Types of One-Way Binding in Blazor
1. Simple Property Binding
The most basic form of one-way binding uses the @ symbol to render C# values in HTML.
@page "/simple-binding"
<h1>@title</h1>
<p>Current count: @count</p>
<p>Current time: @DateTime.Now</p>
@code {
private string title = "Welcome to Blazor";
private int count = 42;
}
How it works:
- The
@ symbol tells Razor to evaluate the C# expression - When
title or count changes in code, the UI updates automatically - The UI cannot directly modify these values
2. Expression Binding
Use @() to evaluate more complex C# expressions inline.
<p>Double count: @(count * 2)</p>
<p>Is even: @(count % 2 == 0 ? "Yes" : "No")</p>
<p>Full name: @($"{firstName} {lastName}")</p>
<p>Item count: @(items?.Count ?? 0)</p>
<p>Uppercase: @(message.ToUpper())</p>
@code {
private int count = 5;
private string firstName = "John";
private string lastName = "Doe";
private string message = "hello world";
private List<string>? items = new() { "A", "B", "C" };
}
Expression examples:
- Arithmetic:
@(price * quantity) - Ternary:
@(isActive ? "Active" : "Inactive") - String interpolation:
@($"Hello, {name}!") - Null coalescing:
@(value ?? "Default") - Method calls:
@(GetFormattedDate())
3. Attribute Binding
Bind C# values to HTML element attributes.
<input type="text" value="@inputValue" />
<input type="text" placeholder="@placeholderText" />
<button disabled="@isDisabled">Submit</button>
<a href="@linkUrl">@linkText</a>
<img src="@imageSource" alt="@imageAlt" />
<div id="@elementId" class="@cssClass">Content</div>
@code {
private string inputValue = "Initial value";
private string placeholderText = "Enter text...";
private bool isDisabled = false;
private string linkUrl = "https://blazor.net";
private string linkText = "Visit Blazor";
private string imageSource = "/images/logo.png";
private string imageAlt = "Logo";
private string elementId = "myElement";
private string cssClass = "container";
}
Important notes:
- Boolean attributes like
disabled work automatically - Empty strings are treated as truthy for boolean attributes
- Use
null to omit an attribute entirely
4. CSS Class Binding
Dynamically apply CSS classes based on component state.
<!-- Direct binding -->
<div class="@containerClass">Content</div>
<!-- Conditional binding -->
<div class="@(isActive ? "active" : "inactive")">Status</div>
<!-- Multiple classes -->
<div class="base-class @additionalClass @(isHighlighted ? "highlighted" : "")">
Mixed classes
</div>
<!-- Method-based -->
<div class="@GetDynamicClasses()">Dynamic classes</div>
@code {
private string containerClass = "card shadow";
private bool isActive = true;
private string additionalClass = "mt-3";
private bool isHighlighted = false;
private string GetDynamicClasses()
{
var classes = new List<string> { "base" };
if (isActive) classes.Add("active");
if (isHighlighted) classes.Add("highlighted");
return string.Join(" ", classes);
}
}
5. Style Binding
Dynamically apply inline styles.
<!-- Full style string -->
<div style="@dynamicStyle">Styled content</div>
<!-- Individual properties -->
<div style="background-color: @bgColor; color: @textColor; padding: @padding;">
Individual styles
</div>
<!-- Calculated values -->
<div style="width: @(widthPercent)%; opacity: @(opacity / 100.0);">
Calculated styles
</div>
@code {
private string dynamicStyle = "background-color: #f0f0f0; border: 1px solid #ccc;";
private string bgColor = "#007bff";
private string textColor = "white";
private string padding = "20px";
private int widthPercent = 75;
private int opacity = 80;
}
6. Conditional Rendering
Control what gets rendered based on conditions.
@if (isLoggedIn)
{
<p>Welcome, @username!</p>
<button @onclick="Logout">Logout</button>
}
else
{
<p>Please log in to continue.</p>
<button @onclick="Login">Login</button>
}
@if (items.Count > 0)
{
<ul>
@foreach (var item in items)
{
<li>@item</li>
}
</ul>
}
else
{
<p>No items found.</p>
}
@code {
private bool isLoggedIn = false;
private string username = "John";
private List<string> items = new() { "Item 1", "Item 2" };
private void Login() => isLoggedIn = true;
private void Logout() => isLoggedIn = false;
}
7. Loop Binding with @foreach
Render collections of data.
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Price</th>
<th>Category</th>
</tr>
</thead>
<tbody>
@foreach (var product in products)
{
<tr>
<td>@product.Name</td>
<td>$@product.Price.ToString("N2")</td>
<td>@product.Category</td>
</tr>
}
</tbody>
</table>
@code {
private List<Product> products = new()
{
new Product { Name = "Laptop", Price = 999.99m, Category = "Electronics" },
new Product { Name = "Book", Price = 29.99m, Category = "Education" },
new Product { Name = "Coffee", Price = 4.99m, Category = "Food" }
};
public class Product
{
public string Name { get; set; } = "";
public decimal Price { get; set; }
public string Category { get; set; } = "";
}
}
Best practice: Use @key directive for efficient list updates:
@foreach (var product in products)
{
<ProductCard @key="product.Id" Product="@product" />
}
Parent to Child One-Way Binding
One of the most powerful uses of one-way binding is passing data from parent components to child components via parameters.
Child Component Definition
<!-- DisplayCard.razor -->
<div class="card @(IsHighlighted ? "border-primary" : "")">
<div class="card-header">
<h5>@Title</h5>
</div>
<div class="card-body">
<p class="display-4 text-center">@Count</p>
</div>
</div>
@code {
[Parameter]
public string Title { get; set; } = "Default Title";
[Parameter]
public int Count { get; set; }
[Parameter]
public bool IsHighlighted { get; set; }
// This component can only READ these values.
// It cannot modify the parent's state directly.
}
Parent Component Usage
<!-- ParentPage.razor -->
@page "/parent"
<h1>Parent Component</h1>
<div class="mb-3">
<label>Title:</label>
<input type="text" @oninput="@(e => cardTitle = e.Value?.ToString() ?? "")"
value="@cardTitle" class="form-control" />
</div>
<div class="mb-3">
<label>Count:</label>
<input type="number" @oninput="@(e => cardCount = int.Parse(e.Value?.ToString() ?? "0"))"
value="@cardCount" class="form-control" />
</div>
<div class="mb-3">
<input type="checkbox" @onchange="@(e => cardHighlight = (bool)(e.Value ?? false))" />
<label>Highlight</label>
</div>
<!-- One-way binding: Parent → Child -->
<DisplayCard Title="@cardTitle"
Count="@cardCount"
IsHighlighted="@cardHighlight" />
@code {
private string cardTitle = "My Card";
private int cardCount = 42;
private bool cardHighlight = false;
}
How It Works
┌─────────────────────────────────────────────────────────────┐
│ PARENT TO CHILD BINDING │
├─────────────────────────────────────────────────────────────┤
│ │
│ Parent Component Child Component │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ cardTitle │ ──[Title]────► │ Title │ │
│ │ cardCount │ ──[Count]────► │ Count │ │
│ │ cardHighlight │ ──[IsHigh]───► │ IsHighlighted │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
│ • Parent owns the state │
│ • Child receives via [Parameter] │
│ • Data flows DOWN only │
│ • Child cannot modify parent's state directly │
│ │
└─────────────────────────────────────────────────────────────┘
Practical Examples
Example 1: Product Card
<!-- ProductCard.razor -->
<div class="card h-100">
<div class="card-body text-center">
<div class="display-1 mb-3">@Product.ImageEmoji</div>
<h5 class="card-title">@Product.Name</h5>
<p class="text-muted">@Product.Category</p>
<h4 class="text-primary">$@Product.Price.ToString("N2")</h4>
@if (Product.IsOnSale)
{
<span class="badge bg-danger">SALE</span>
}
</div>
</div>
@code {
[Parameter, EditorRequired]
public Product Product { get; set; } = null!;
}
Usage:
@foreach (var product in products)
{
<ProductCard Product="@product" />
}
Example 2: Statistics Card
<!-- StatCard.razor -->
<div class="card text-center border-@Color">
<div class="card-body">
<div class="text-@Color mb-2">
<i class="bi bi-@Icon" style="font-size: 2rem;"></i>
</div>
<h6 class="text-muted text-uppercase small">@Title</h6>
<h3 class="mb-0">@Prefix@Value.ToString("N0")@Suffix</h3>
</div>
</div>
@code {
[Parameter, EditorRequired]
public string Title { get; set; } = null!;
[Parameter]
public decimal Value { get; set; }
[Parameter]
public string Icon { get; set; } = "bar-chart";
[Parameter]
public string Color { get; set; } = "primary";
[Parameter]
public string Prefix { get; set; } = "";
[Parameter]
public string Suffix { get; set; } = "";
}
Usage:
<div class="row">
<div class="col-md-3">
<StatCard Title="Total Users" Value="@totalUsers" Icon="people" Color="primary" />
</div>
<div class="col-md-3">
<StatCard Title="Revenue" Value="@revenue" Icon="currency-dollar" Color="success" Prefix="$" />
</div>
<div class="col-md-3">
<StatCard Title="Growth" Value="@growth" Icon="graph-up" Color="info" Suffix="%" />
</div>
</div>
Example 3: Progress Bar
<!-- ProgressBar.razor -->
<div class="mb-2">
<div class="d-flex justify-content-between mb-1">
<span>@Label</span>
@if (ShowPercentage)
{
<span>@Progress%</span>
}
</div>
<div class="progress" style="height: @(Height)px;">
<div class="progress-bar bg-@Color @(IsAnimated ? "progress-bar-striped progress-bar-animated" : "")"
role="progressbar"
style="width: @Progress%"
aria-valuenow="@Progress"
aria-valuemin="0"
aria-valuemax="100">
</div>
</div>
</div>
@code {
[Parameter]
public int Progress { get; set; }
[Parameter]
public string Label { get; set; } = "";
[Parameter]
public string Color { get; set; } = "primary";
[Parameter]
public bool ShowPercentage { get; set; } = true;
[Parameter]
public bool IsAnimated { get; set; } = true;
[Parameter]
public int Height { get; set; } = 20;
}
Usage:
<ProgressBar Progress="@downloadProgress"
Label="Downloading..."
Color="@GetProgressColor()"
ShowPercentage="true" />
@code {
private int downloadProgress = 65;
private string GetProgressColor() => downloadProgress switch
{
>= 75 => "success",
>= 50 => "info",
>= 25 => "warning",
_ => "danger"
};
}
One-Way Binding with Events
While the data flows one way, you often need to respond to user interactions. This is done with event handlers.
@page "/counter"
<h1>Counter: @count</h1>
<!-- One-way binding displays the value -->
<p>The count is: @count</p>
<!-- Events allow user interaction -->
<button @onclick="Increment">Increment</button>
<button @onclick="Decrement">Decrement</button>
<button @onclick="Reset">Reset</button>
<!-- Input with explicit event handling -->
<input type="number" value="@count" @oninput="HandleInput" />
@code {
private int count = 0;
private void Increment() => count++;
private void Decrement() => count--;
private void Reset() => count = 0;
private void HandleInput(ChangeEventArgs e)
{
if (int.TryParse(e.Value?.ToString(), out var value))
{
count = value;
}
}
}
Key Point: The input's value attribute is one-way bound. The @oninput event explicitly updates the source. This is different from two-way binding (@bind) which does both automatically.
One-Way vs Two-Way Binding
| Aspect One-Way Binding Two-Way Binding |
| Syntax | value="@myValue" | @bind="myValue" |
| Data Flow | Source → UI only | Source ↔ UI |
| UI Updates Source | No (needs event handler) | Yes (automatic) |
| Control | More explicit | More convenient |
| Performance | Slightly better | Slightly more overhead |
| Debugging | Easier to trace | Can be harder to trace |
| Use Case | Display, read-only | Forms, input fields |
When to Use One-Way Binding
- Display-only data: Labels, headings, statistics
- Computed values: Calculated from other data
- Parent to child communication: Component parameters
- Performance-critical scenarios: Avoid unnecessary updates
- Complex validation: When you need control over when/how data updates
When to Use Two-Way Binding
- Form inputs: Text fields, checkboxes, selects
- Simple data entry: Quick prototyping
- User preferences: Settings that update immediately