In the last chapter, we learned all the basics of creating a component. This chapter will teach us how to take our components to the next level.
This chapter will focus on some of the features that will make our components reusable, which will enable us to save time and also give us an understanding of how to use reusable components made by others.
We will also look at some built-in components that will help you by adding additional functionality (compared to using HTML tags) when you build your Blazor app.
In this chapter, we will cover the following topics:
In this chapter, we will start building our components. For this, you’ll need the code we developed in Chapter 4, Understanding Basic Blazor Components. You are good to go if you followed the instructions in the previous chapters. If not, then make sure you clone/download the repo. The starting point for this chapter can be found in the chapter04
folder, and the finished chapter is in chapter05
.
You can find the source code for this chapter’s result at https://github.com/PacktPublishing/Web-Development-with-Blazor-Second-Edition/tree/main/Chapter05.
When building applications, data is important, and we can use binding to show or change data. Using binding, you can connect variables within a component (so that it updates automatically) or by setting a component attribute. Perhaps the most fantastic thing is that by using binding, Blazor understands when it should update the UI and the variable (if the data changes in the UI).
In Blazor, there are two different ways that we can bind values to components, as follows:
By using binding, we can send information between components and make sure we can update a value when we want to.
We have already discussed one-way binding in Chapter 4, Creating Basic Blazor Components. Let’s look at the component again and continue building on it in this section.
In this section, we will combine parameters and binding.
The Counter.razor example looks like this:
@page "/counter"
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
The component will show the current count and a button that will increment the current count. This is one-way binding. Even though the button can change, the value of currentCount
only flows in one direction.
Since this part is designed to demonstrate the functionality and theory and is not part of the finished project we are building, you don’t have to write or run this code. The source code for these components is available on GitHub.
We can add a parameter to the Counter
component. The code will then look like this:
@page "/counterwithparameter"
<h1>Counter</h1>
<p>Current count: @CurrentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
[Parameter]
public int IncrementAmount { get; set; } = 1;
[Parameter]
public int CurrentCount { get; set; } = 0;
private void IncrementCount()
{
CurrentCount+=IncrementAmount;
}
}
The code sample has two parameters, one for CurrentCount
and one for IncrementAmount
. By adding parameters to the components, we can change their behavior. This sample is, of course, a bit silly. The chances are that you won’t have any use for a component like this, but it illustrates the idea very well.
We can now take the component and add it to another component. This is how we can create a reusable component and change its behavior by changing the value of the parameters.
We change its behavior like this:
@page "/parentcounter"
<CounterWithParameter IncrementAmount="@incrementamount" CurrentCount="@currentcount"></CounterWithParameter>
The current count is: @currentcount
@code {
int incrementamount = 10;
int currentcount = 0;
}
In this sample, we have two variables, incrementamount
and currentcount
, that we pass into our CounterWithParameter
component.
If we were to run this, we would see a Counter
component that counts in increments of 10. However, the currentcount
variable will not be updated since it is only a one-way binding (one direction).
To help us with that, we can implement two-way binding so that our parent component will be notified of any changes.
Two-way binding binds values in both directions, and our Counter
component will be able to notify our parent component of any changes. In the next chapter, Chapter 6, Building Forms with Validation, we will talk even more about two-way binding.
To make our CounterWithParameter
component bind in two directions, we need to add EventCallback
. The name must consist of the parameter’s name followed by Changed
. This way, Blazor will update the value if it changes. In our case, we would need to name it CurrentCountChanged
. The code would then look like this:
[Parameter]
public EventCallback<int> CurrentCountChanged { get; set; }
private async Task IncrementCount()
{
CurrentCount += IncrementAmount;
await CurrentCountChanged.InvokeAsync(CurrentCount);
}
By merely using that naming convention, Blazor knows that CurrentCountChanged
is the event that will get triggered when a change to CurrentCount
occurs.
EventCallback
cannot be null
, so there is no reason to do a null check (more on that in the next section).
We also need to change how we listen for changes:
<CounterWithParameterAndEvent IncrementAmount="@incrementamount" @bind-CurrentCount="currentcount"/>
We need to add @bind-
before the CurrentCount
binding. You can also use the following syntax to set the name of the event:
<CounterWithParameterAndEvent IncrementAmount="@incrementamount" @bind-CurrentCount="currentcount" @bind-CurrentCount:event="CurrentCountChanged"/>
By using :event
, we can tell Blazor exactly what event we want to use; in this case, the CurrentCountChanged
event.
In the next chapter, Chapter 6, Building Forms with Validation, we will continue to look at binding with input/form components.
We can, of course, create events as well using EventCallback.
To communicate changes, we can use EventCallback, as shown in the Two-way binding section. EventCallback<T>
differs a bit from what we might be used to in .NET. EventCallback<T>
is a class that is specially made for Blazor to be able to have the event callback exposed as a parameter for the component.
In .NET, in general, you can add multiple listeners to an event (multi-cast), but with EventCallback<T>
, you will only be able to add one listener (single-cast).
It is worth mentioning that you can use events the way you are used to from .NET in Blazor. However, you probably want to use EventCallback<T>
because there are many upsides to using EventCallback
over traditional .NET events.
.NET events use classes, and EventCallback
uses structs. This means that in Blazor, we don’t have to perform a null check before calling EventCallback
because a struct cannot be null.
EventCallback
is asynchronous and can be awaited. When EventCallback
has been called, Blazor will automatically execute StateHasChanged
on the consuming component to ensure the component updates (if it needs to be updated).
So, if you require multiple listeners, you can use Action<T>
. Otherwise, it would be best if you used EventCallback<T>
.
Some event types have event arguments that we can access. They are optional, so you don’t need to add them in most cases.
You can add them by specifying them in a method, or you can use a lambda expression like this:
<button @onclick="@((e)=>message=$"x:{e.ClientX} y:{e.ClientY}")">Click me</button>
When the button
is clicked, it will set a variable called message
to a string containing the mouse coordinates. The lambda has one parameter, e
, of the MouseArgs
type. However, you don’t have to specify the type, and the compiler understands what type the parameter is.
Now that we have added actions and used EventCallback to communicate changes, we will see how we can execute RenderFragment in the next section.
To make our components even more reusable, we can supply them with a piece of Razor syntax. In Blazor, you can specify RenderFragment, which is a fragment of Razor syntax that you can execute and show.
Now that we have added actions and used EventCallback to communicate changes, we will see how we can execute RenderFragment in the next section.
There are two types of render elements, RenderFragment
and RenderFragment<T>
. RenderFragment
is simply a Razor fragment without any input parameters, and RenderFragment<T>
has an input parameter that you can use inside the Razor fragment code by using the context
keyword. We won’t go into depth about how to use this now, but later in this chapter, we will talk about a component (Virtualize) that uses RenderFragment<T>
and, in the next chapter, Chapter 6, Building Forms with Validation, we will implement a component using RenderFragment<T>
.
We can make RenderFragment
the default content inside of the component tags as well as giving it a default value. We will explore this next and build a component using these features.
GRID COMPONENT
If you want to dig deeper into render fragments, please check out Blazm.Components, which have a grid component that heavily uses RenderFragment<T>
. Where I currently work, we use this component, and it has been developed using real-world scenarios.
You can find it on GitHub here: https://github.com/EngstromJimmy/Blazm.Components.
By naming the render fragment ChildContent
, Blazor will automatically use whatever is between the component tags as content. This only works, however, if you are using a single render fragment; if you are using more than one, you will have to specify the ChildComponent
tag as well.
We can supply RenderFragment
with a default value or set it in code by using an @ symbol:
@<b>This is a default value</b>;
To better understand how to use render fragments, let’s build an alert component. The built-in templates use Bootstrap, so we will do the same for this component. Bootstrap has many components that are easy to import to Blazor. When working on big projects with multiple developers, building components is an easy way to ensure that everyone in one team is writing code the same way.
Let’s build a simple alert component based on Bootstrap:
RazorComponents
.Alert.razor
.Alert.razor
file:
<div class="alert alert-primary" role="alert">
A simple primary alert—check it out!
</div>
The code is taken from Bootstrap’s web page, http://getbootstrap.com, and it shows an alert that looks like this:
Figure 5.1: The default look of a Bootstrap alert component
There are two ways in which we could customize this alert
component. We could add a string
parameter for the message.
However, since this is a section on render fragments, we will explore the second option, yes, you guessed it, render fragments.
RenderFragment
property called ChildContent
and replace the alert text with the new property:
<div class="alert alert-primary" role="alert">
@ChildContent
</div>
@code{
[Parameter]
public RenderFragment ChildContent { get; set; } =@<b>This is a default value</b>;
}
Now we have a RenderFragment
and set a default value, displaying the fragment between the div
tags. We also want to add an enum
for the different ways you can style the alert box.
code
section, add an enum
containing the different styles available:
public enum AlertStyle
{
Primary,
Secondary,
Success,
Danger,
Warning,
Info,
Light,
Dark
}
enum
style:
[Parameter]
public AlertStyle Style { get; set; }
class
attribute for div
. Change the class
attribute to look like this:
<div class="@($"alert alert-{Style.ToString().ToLower()}")" role="alert">
Pages
folder, add a new Razor component, and name it AlertTest.razor
.
Replace the code with the following snippet:
@page "/alerttest"
@using Components.RazorComponents
<Alert Style="Alert.AlertStyle.Danger">
This is a test
</Alert>
<Alert Style="Alert.AlertStyle.Success">
<ChildContent>
This is another test
</ChildContent>
</Alert>
<Alert Style="Alert.AlertStyle.Success"/>
The page shows three alert components:
The first one has the Danger
style, and we are not specifying what property to set for the This is a test
text, but by convention, it will use the property called ChildContent
.
In the second one, we have specified the ChildContent
property. If you use more render fragments in your component, you must set them like this, with full names.
In the last one, we didn’t specify anything that will give the property the default render fragment we specified in the component.
/AlertTest
to see the test page:Figure 5.2: Screenshot of the test page
We have finished our first reusable component!
Creating reusable components is how I prefer to make my Blazor sites because I don’t have to write the same code twice. This becomes even more apparent if you are working in a larger team. It makes it easier for all developers to produce the same code and end result, and with that, they can get a higher code quality and require fewer tests.
When we upgraded to the latest Bootstrap version, a few CSS classes were deprecated and replaced by others. Thankfully, we followed this approach by making reusable components, so we only had to change a handful of places. There were a couple of places where we still had some old code base (not using components), and it became very apparent that creating components was worth the effort.
Blazor has a bunch of built-in components. In the next section, we will dig deeper into what they are and how to use them.
When Blazor first came out, there were a couple of things that were hard to do, and, in some cases, we needed to involve JavaScript to solve the challenge. In this section, we will look at some of the new components we got in .NET 5 and .NET 6.
We will take a look at the following new components or functions:
One of my first Blazor blog posts was about how to set the focus on a UI element, but now this is built into the framework. The previous solution involved JavaScript calls to change the focus on a UI element.
By using ElementReference
, you can now set the focus on the element.
Let’s build a component to test the behavior of this new feature:
Pages
folder, add a new Razor component, and name it SetFocus.razor
.SetFocus.razor
and add a page
directive:
@page "/setfocus"
@code {
ElementReference textInput;
}
ElementReference
is precisely what it sounds like, a reference to an element. In this case, it is an input textbox.
<input @ref="textInput" />
<button @onclick="() => textInput.FocusAsync()">Set focus</button>
Using @ref
, we specify a reference to any type of component or tag that we can use to access the input box. The button onclick
method will execute the FocusAsync()
method and set the focus on the textbox.
/setfocus
.It could seem like a silly example since this only sets the focus, but it is a handy feature, and the autofocus
HTML attribute won’t work for Blazor.
In my blog post, I had another approach. My goal was to set the focus of an element without having to use code. In the upcoming chapter, Chapter 6, Building Forms with Validation, we will implement the autofocus
feature from my blog post but use the new .NET features instead.
The release of .NET 5 solves many things we previously had to write with JavaScript; setting the focus is one example. In .NET 6, we have a way to influence the HTML head.
Sometimes, we want to set our page’s title or change the social network meta tags. The head
tag is located in _host.cshtml
(Blazor Server) and index.html
(Blazor WebAssembly), and that part of the page isn’t reloaded/rerendered (only the components within the app component are rerendered). In previous versions of Blazor, you had to write code for that yourself using JavaScript.
But .NET has a new component called HeadOutlet that can solve that.
To use these components, we will create a page to view one of our blog posts. And we will use many of the techniques we have learned:
foreach
loop to look like this:
<li><a href="/Post/@p.Id">@p.Title</a></li>
We added a link to the title to look at one blog post. Notice how we can use the @
symbol inside the href
attribute to get the ID of the post.
Pages
folder, add a Razor component, and name it Post.razor
.code
section, add a parameter that will hold the ID of the post:
[Parameter]
public string BlogPostId { get; set; }
This will hold the ID of the blog post that comes from the URL.
page
directive to get the set, the URL, and the ID:
@page "/post/{BlogPostId}"
The page
directive will set the URL for our blog post to /post/
, followed by the ID of the post. We don’t have to add a using
statement to all our components. Instead, open _Imports.razor
and add the following namespaces:
@using Data.Models.Interfaces
@using Data.Models
This will ensure that all our components will have these namespaces by default.
Post.razor
again and, just beneath the page
directive, inject the API (the namespace is now supplied from _Imports.razor
):
@inject IBlogApi _api
@inject NavigationManager _navman
Our API will now be injected into the component, and we can retrieve our blog post. We also have access to a navigation manager.
code
section, add a property for our blog post:
public BlogPost? BlogPost { get; set; }
This will contain the blog post we want to show on the page.
protected async override Task OnParametersSetAsync()
{
BlogPost=await _api.GetBlogPostAsync(BlogPostId);
await base.OnParametersSetAsync();
}
In this case, we are using the OnParametersSetAsync()
method. This is to make sure that the parameter is set when we get data from the database and that the content updates when the parameter changes.
meta
tags. To do that, add the following code just above the code section:
@if (BlogPost != null)
{
<PageTitle>@BlogPost.Title</PageTitle>
<HeadContent>
<meta property="og:title"
content="@BlogPost.Title" />
<meta property="og:description" content="@(new
string(BlogPost.Text.Take(100).ToArray()))" />
<meta property="og:image" content=
"@($"{_navman.BaseUri}/pathtoanimage.png")" />
<meta property="og:url" content="@_navman.Uri" />
<meta name="twitter:card" content="@(new string(BlogPost.Text.Take(100).ToArray()))" />
</HeadContent>
<h2>@BlogPost.Title</h2>
@((MarkupString)BlogPost.Text)
}
When the page is first loaded, the BlogPost
parameter can be null, so we first need to check whether we should show the content at all.
By adding the Title
component, Blazor will set the title of our site to, in this instance, the title of our blog post.
According to the information I gathered on Search Engine Optimization (SEO), the meta tags we have added are the bare minimum to use with Facebook and Twitter. We don’t have an image for each blog post, but we can have one that is site-wide (for all blog posts) if we would like. Just change Pathtoanimage.png
to the name of the image and put the image in the wwwroot
folder.
If the blog post is loaded, then show an H3 tag with the title and the text beneath that. You might remember MarkupString from Chapter 4, Understanding Basic Blazor Components. This will output the string from our blog post without changing the HTML (not escaping the HTML).
Figure 5.3: Blog post screenshot
Our blog is starting to take form. We have a list of blog posts, and can view a single post; we are far from done but we’re well on our way.
Virtualize is a component in Blazor that will make sure that it only renders the components or rows that can fit the screen. If you have a large list of items, rendering all of them will have a big impact on memory.
Many third-party component vendors offer grid components with the same virtualization function. The Virtualize component is, in my opinion, the most exciting thing in the .NET 5 release.
The Virtualize component will calculate how many items can fit on the screen (based on the size of the window and the height of an item). Blazor will add a div
tag before and after the content list if you scroll the page, ensuring that the scrollbar is showing the correct position and scale (even though there are no items rendered).
The Virtualize
component works just like a foreach
loop.
The following is the code we currently have in our Index.razor
file:
<ul>
@foreach (var p in posts)
{
<li><a href="/Post/@p.Id">@p.Title</a></li>
}
</ul>
Right now, it will show all our blog posts in our database in a long list. Granted, we only have a few right now, but we might have many posts one day.
We can change the code (don’t change the code just yet) to use the new Virtualize
component by changing it to the following:
<Virtualize Items="posts" Context="p">
<li><a href="/Post/@p.Id">@p.Title</a></li>
</Virtualize>
Instead of the foreach
loop, we use the Virtualize component and add a render fragment that shows how each item should be rendered. The Virtualize component uses RenderFragment<T>
, which, by default, will send in an item of type T
to the render fragment. In the case of the Virtualize component, the object will be one blog post (since items are List<T>
of blog posts). We access each post with the variable named context
. However, we can use the Context property on the Virtualize component to specify another name, so instead of context
, we are now using p
.
The Virtualize component is even more powerful than this, as we will see in the next feature that we implement:
OnInitializedAsync
method and protected List<BlogPost> posts = new List<BlogPost>()
; we don’t need them anymore.Virtualize
:
<ul>
<Virtualize ItemsProvider="LoadPosts" Context="p">
<li><a href="/Post/@p.Id">@p.Title</a></li>
</Virtualize>
</ul>
In this case, we are using the ItemsProvider
delegate, which will take care of getting posts from our API.
We pass in a method called LoadPosts
, which we also need to add to the file.
LoadPosts
method by adding the following code:
public int totalBlogposts { get; set; }
private async ValueTask<ItemsProviderResult<BlogPost>> LoadPosts(ItemsProviderRequest request)
{
if (totalBlogposts == 0)
{
totalBlogposts = await _api.GetBlogPostCountAsync();
}
var numblogposts = Math.Min(request.Count, totalBlogposts - request.StartIndex);
var blogposts= await _api.GetBlogPostsAsync(numblogposts,request.StartIndex);
return new ItemsProviderResult<BlogPost>(blogposts, totalBlogposts);
}
We add a totalBlogposts
property where we store how many posts we currently have in our database. The LoadPost
method returns ValueTask
with ItemsProviderResult<Blogpost>
. The method has ItemsProviderRequest
as a parameter, which contains the number of posts the Virtualize component wants and how many it wants to skip.
If we don’t know how many total posts we have, we need to retrieve that information from our API by calling the GetBlogPostCountAsync
method. Then, we need to figure out how many posts we should get; either we get as many posts as we need, or we get all the remaining posts (whatever value is the smallest).
Then, we call our API to get the actual posts by calling GetBlogPostsAsync
and returning ItemsProviderResult
.
Now we have implemented a Virtualize component that will load and render only the number of blog posts needed to fill the screen.
In .NET 6, we got a very handy component to handle errors called ErrorBoundary.
We can surround the component with an ErrorBoundary
component; if an error occurs, it will show an error message instead of the whole page failing:
<ErrorBoundary>
<ComponentWithError />
</ErrorBoundary>
We can also supply a custom error message like this:
<ErrorBoundary>
<ChildContent>
<ComponentWithError />
</ChildContent>
<ErrorContent>
<h1 style="color: red;">Oops... something broke</h1>
</ErrorContent>
</ErrorBoundary>
This is a great component to extend and create your own functionality. You can get access to the exception by using the context
parameter (as we did with virtualize
):
<ErrorBoundary Context=ex>
<ChildContent>
<p>@(1/zero)</p>
</ChildContent>
<ErrorContent>
An error occurred
@ex.Message
</ErrorContent>
</ErrorBoundary>
@code {
int zero = 0;
}
This is a great way to handle errors in the UI.
In this chapter, we looked at more advanced scenarios for building components. Building components is what Blazor is all about. Components also make it easy to make changes along the way because there is only one point where you must implement the change. We also implemented our first reusable component, which will help maintain the same standard across the team and reduce duplicated code.
We also used some Blazor features to load and display data.
In the next chapter, we will look at forms and validation to start building the administration part of our blog.