Blazor Custom Component

In this blog I have already talked about creating components but now I want to start from the beginning and explain how to write a reusable Blazor component from scratch. This is the first part of a Blazor article series to tell how to create a reusable Blazor component. So, we can use them in our Blazor application independently of the hosting model (Web Assembly or Server-side). If you google a bit, you can find a lot of example like that but I can’t find one example that explain exactly how the interaction works. Here, step by step what I understood.

Then, for more documentation, example and components about Blazor, here same links in this blog:

Also, you can download the source code of this post from GitHub.

So, there result of this post is a simple button. When you click the button, Blazor calls the code in a javascript file and runs it. The result is passed to the C# code and then as result of the component.

Start!

First, I have to create a new Blazor project. For that, Create a new project and search for Blazor. Then, select Blazor WebAssembly App.

Visual Studio 2019 - Create a new project - Write a reusable Blazor component
Visual Studio 2019 – Create a new project

Then, you have to select the name of the project and its location. The name of my project is PSCBlazor and you can find it on GitHub.

Configure a new project - Write a reusable Blazor component
Configure a new project

Select as Target Framework .NET 5. Then, Create.

Addition Information - Write a reusable Blazor component
Addition Information

Now, we have the main project. But to write a reusable Blazor component, we have to create another project for it. Following the same procedure, create a new project but this time select Razor Class Library.

Create a Razor Class Library - Write a reusable Blazor component
Create a Razor Class Library

Then, follow the instruction. The name of my project is PSCBlazor.SimpleComponent.

If you don’t want to use Visual Studio, you can run from the Command Line

dotnet new razorclasslib

By default, the created project contains several elements:

A predefined “Component1” that is a basic component displaying some styled text in Component1.razor.

  • ExampleJsInterop class that demonstrate how to call a basic JS function defined in its own js file in ExampleJsInterop.cs.
  • The static resources (like png, css and js files) in the “wwwroot” folder:
    – background.png
    – exampleJsInterop.js
    – styles.css
The solution for the custom component
The solution for the custom component

Separate the code from the razor code

Behind the scenes, when you write a razor page, an actual C# class is generated and hopefully the class is partial. It means that it is easy to separate the behavioral code of your component from the html view code.

Single mixed file Component1.razor:

<div class="my-component">
    This Blazor component is defined in the <strong>PSCBlazor.SimpleComponent</strong> package.
</div>

@code {
}

Sometimes it is nice to separate the view from the code. In Visual Studio is pretty simple. Create a new Class with the full name of the view file plus cs. For example, if the view is Component1.razor the new file should be Component.razor.cs and automatically Visual Studio understands that this file is part of the view.

Split view and code - Write a reusable Blazor component
Split view and code

Then, a little change in the code because the new class must be a partial class. For example:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace PSCBlazor.SimpleComponent
{
	public partial class Component1
	{
	}
}

Interop between Blazor and JavaScript

Now, I write a reusable component for Blazor and I’m going to modify the component to be able to exchange some data between the component itself and the parent. For that, I have to use a JS function through the generated ExampleJsInterop to prompt the user for a text input.

The component will be used this way with TextValue being a property used in the binding:

<Component1
    ParentName=”Some text I want to transmit from the parent to the child component”
    @bind-Text=”TextValue”>
</Component1>

Define the parameters

We need to define a property ParentName to transmit some data from the parent to the child component:

[Parameter]
public string ParentName { get; set; }

The Parameter attribute tells that the C# property can be used as component parameter.

Now, I need to define a parameter to be able to transmit data to the child component, but we also want to get data from the component using a @bind property.

To make it possible, I have to define a parameter like the previous one named in our case Text.

[Parameter]
public string Text { get; set; }

Define a EventCallback parameter to be able to notify the binding engine that the value has changed. As a convention, the name of the event callback property must be named accordingly to the property you want to bind. In our case it must be named TextChanged.

[Parameter]
public EventCallback<string> TextChanged { get; set; }

The name of the event callback can be named differently but in that case it is required to specify it when binding a value using

@bind-{Property}:event=”NameOfPropertyChanged”

It means that the two notation are equivalent when binding a value:

<Child @bind-MyProperty="Text" />
<Child @bind-MyProperty="Text" @bind-MyProperty:event="TextChanged" />

Once the properties are defined, the component will have to invoke the event callback when the property value is changing.

await TextChanged.InvokeAsync(valueOfUserTextInputFromModalDialog);

Inject the appropriate services

To use the Java Script interop and the ExampleJsInterop example class we have to use an injected instance of IJSRuntime. We can do it with a dedicated attribute Inject in the component C# file.

Implement the behavior

Now, I have all we need to implement the behavior by adding a button with a click handler that is going to call the JS function through the ExampleJsInterop.Prompt method. Once we got the result we just need to forward to the TextChanged event call back and to update our Text property value.

So, in the razor file I created a bit of HTML, just to have a bit more fun.

<div class="my-component">
	<div class="my-buttonkeeper">
		<button class="my-button" @onclick="GetTextAsync">Click me</button>
	</div>
</div>
<br />
<text>Text (in the component) : @Text</text>
<br />

Then, in the Component1.razor.css I added some CSS code

.my-component {
    border: 2px dashed red;
    padding: 1em;
    margin: 1em 0;
    background-image: url('background.png');
}

.my-buttonkeeper {
    text-align: center;
}

.my-button {
    width: 150px;
    height: 150px;
    background-color: green;
    color: white;
    font-size: 20px;
}

Why? Because I want to be sure the main application adds the CSS from this component but also the javascript we need. I’ll explain that in a second.

Then, it is the moment of the CS file:

private async Task GetTextAsync()
{
    // Call JS interop.
    var text = await new ExampleJsInterop(JsRuntime).Prompt("Enter some text:");

    // Trigger the changed event.
    await TextChanged.InvokeAsync(text);

    // Set the property value.
    Text = text;
}

Inside wwwroot directory we have ready to use exampleJsInterop.js file which was created by Visual Studio or CLI. The code is like that

// This is a JavaScript module that is loaded on demand. It can export any number of
// functions, and may import other JavaScript modules if required.

export function showPrompt(message) {
  return prompt(message, 'Type anything here');
}

This is a simple javascript function. This rings a bell to me, it is like in React. Now, the magic is happening in the ExampleJsInterop.cs

using Microsoft.JSInterop;
using System;
using System.Threading.Tasks;

namespace PSCBlazor.SimpleComponent
{
	// This class provides an example of how JavaScript functionality can be wrapped
	// in a .NET class for easy consumption. The associated JavaScript module is
	// loaded on demand when first needed.
	//
	// This class can be registered as scoped DI service and then injected into Blazor
	// components for use.

	public class ExampleJsInterop : IAsyncDisposable
	{
		private readonly Lazy<Task<IJSObjectReference>> moduleTask;

		public ExampleJsInterop(IJSRuntime jsRuntime)
		{
			moduleTask = new(() => jsRuntime.InvokeAsync<IJSObjectReference>(
			   "import", "./_content/PSCBlazor.SimpleComponent/exampleJsInterop.js").AsTask());
		}

		public async ValueTask<string> Prompt(string message)
		{
			var module = await moduleTask.Value;
			return await module.InvokeAsync<string>("showPrompt", message);
		}

		public async ValueTask DisposeAsync()
		{
			if (moduleTask.IsValueCreated)
			{
				var module = await moduleTask.Value;
				await module.DisposeAsync();
			}
		}
	}
}

So, between the lines 24 and 28 there is a call via IJSObjectReference to the javascript function in the file under the wwwroot. You can see that in line 21 where there is an import of the javascript file.

Run the demo project

So, almost finish to write a reusable Blazor component with interop with javascript. First, I have to add the component as dependency in the project. In Solution Explorer, right click on Dependecies in the PSCBlazor and click on Add project references… Then, select the component.

Reference manager
Reference manager

Now, open _Imports.razor and add the reference to this component to add this line

@using PSCBlazor.SimpleComponent 

Then, open Index.razor and add this code

<Component1 ParentName="This is a test" @bind-Text="_text"></Component1>

The text from the component is: @_text

@code {
	private string _text = "";
}

Also, we want to add the CSS and the javascript for the magic. Depending on the hosting model, it is required to add the reference to the CSS and javascript in specific index files in the application project:

wwwroot/index.html for the Web Assembly hosting model.

and

Pages/_Host.cshtml for the Server-side hosting model.

So, open the index.html under wwwroot and add those lines (I deleted a lot of lines for the purpose of showing what you have to add):

<head>
    <link href="_content/PSCBlazor.SimpleComponent/styles.css" rel="stylesheet" />
</head>

<body>
    <script src="_framework/blazor.webassembly.js"></script>
    <script src="_content/PSCBlazor.SimpleComponent/exampleJsInterop.js" />
</body>

Basically, in the head I have to add the reference to the CSS from the component. Similarly, I have to do the same to add the javascript.

Compile and run. If everything is ok, you have the same result as I show at the top of this post.

By Enrico

My greatest passion is technology. I am interested in multiple fields and I have a lot of experience in software design and development. I started professional development when I was 6 years. Today I am a strong full-stack .NET developer (C#, Xamarin, Azure)

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.