5

Creating Advanced Blazor Components

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:

  • Exploring binding
  • Actions and EventCallback
  • Using RenderFragment
  • Exploring the new built-in components

Technical requirements

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.

Exploring binding

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:

  • One-way binding
  • Two-way binding

By using binding, we can send information between components and make sure we can update a value when we want to.

One-way binding

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

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.

Actions and 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.

Using RenderFragment

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.

ChildContent

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.

Default value

We can supply RenderFragment with a default value or set it in code by using an @ symbol:

@<b>This is a default value</b>;

Building an alert component

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:

  1. Create a folder by right-clicking on Components project | Add | New folder and name the folder RazorComponents.
  2. Create a new Razor component and name it Alert.razor.
  3. Replace the content with the following code in the 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

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.

  1. Add a code section with a 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.

  1. In the code section, add an enum containing the different styles available:
    public enum AlertStyle
    {
        Primary,
        Secondary,
        Success,
        Danger,
        Warning,
        Info,
        Light,
        Dark
    }
    
  2. Add a parameter/property for the enum style:
    [Parameter]
    public AlertStyle Style { get; set; }
    
  3. The final step is to update the class attribute for div. Change the class attribute to look like this:
    <div class="@($"alert alert-{Style.ToString().ToLower()}")" role="alert">
    
  4. In the 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.

  1. Run the BlazorServer project and navigate to /AlertTest to see the test page:
Figure 5.2 – Screenshot of 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.

Exploring the new built-in components

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:

  • Setting the focus of the UI
  • Influencing the HTML head
  • Component virtualization
  • Error boundaries

Setting the focus of the UI

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:

  1. In the Components project, in the Pages folder, add a new Razor component, and name it SetFocus.razor.
  2. Open SetFocus.razor and add a page directive:
    @page "/setfocus"
    
  3. Add an element reference:
    @code {
        ElementReference textInput;
    }
    

    ElementReference is precisely what it sounds like, a reference to an element. In this case, it is an input textbox.

  1. Add the textbox and a button:
    <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.

  1. Press F5 to run the project and then navigate to /setfocus.
  2. Press the Set focus button and notice how the textbox gets its focus.

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.

Influencing 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:

  1. In the Components project, open Pages/Index.razor.
  2. Change the 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.

  1. In the Pages folder, add a Razor component, and name it Post.razor.
  2. In the 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.

  1. Add a 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.

  1. Open 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.

  1. In the 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.

  1. To load the blog post, add the following code:
    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.

  1. We must also show the post and add the necessary 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).

  1. Run the project by pressing F5 and navigate to a blog post to see the title change:
Figure 5.3 – Blog post screenshot

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.

Component virtualization

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:

  1. In the Components project, open Pages/Index.razor.
  2. Delete the OnInitializedAsync method and protected List<BlogPost> posts = new List<BlogPost>(); we don’t need them anymore.
  3. Change the loading of the post to 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.

  1. Now, let’s add the 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.

Error boundaries

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.

Summary

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset