When you start a new Blazor project it will quickstart you with a default Bootstrap template. But when you spent several weeks or months on the project you starting to notice that there is a lot of duplicate HTML code that can be refactored and moved into components.
In this post, I want to give you some advice on how and which Bootstrap components you can wrap into .razor
components to make your code simpler, easier to maintain, and expandable.
xakpc / Xakpc.BlazorBits.Bootstrap
Sample project for https://dev.to/xakpc/ article
All these components are alterations of what I use every day in my projects. Why not share the same? In real projects, there is usually some CSS template used that could alter the component significantly.
These examples use clean Bootstrap and can be the foundation that could be altered for your CSS template. That's why I usually just copy-paste them across projects and tune to fit the current CSS template, instead of moving them to separate NuGet package;
Bootstrap has more than 20 components, but for this article, I keep only the ones that used very often and make a great example for you where to go from hereby.
- Button
- Button Group
- Card
- Progress
- Spinner
- Toast
- Modals
Next time - Form and Form Components in Blazor with MVVM
Button
The most basic component is a button. Because I use MVVM my button is based on the ICommand
interface
When you add action to button's ICommand AddCommand = new Command<int>(DoAdd);
take notice of the action
private void DoAdd(int p)
{
Value += p;
StateHasChanged(); // <--- not gonna work without it
}
I found out that onaction
will re-render only the component it belongs to. Even if actual action changes something from another (parent) component. But when you use ViewModel there is no need in StateHasChanged
call here because the View should be subscribed to INotifyPropertyChanged
anyway.
Button Group
Button group wraps several command buttons in a single neat component. I usually use it for data table editing
The component is rather simple, you can use icons instead of text for a better look, but the main thing that it nicely fits Blazor way of building tables.
You could pass commands and current item to ViewModel for processing. If you have a lot of tables in your app that saves tons of space also, you get consistency across tables and can easily adapt styles in the future.
<tbody>
@foreach (var item in Items)
{
<tr>
<td>@item</td>
<td><RowButtonGroup
EditCommand="EditItemCommand"
DeleteCommand="DeleteItemCommand"
Parameter="item"/></td>
</tr>
}
</tbody>
Card
For me, the card is the primary component of any web app. This component wrap most of the basic HTML card into several optional RenderFragments
. You can expand it further to add an optional card-image
or list-group
block.
I noticed that some folks missing the fact that you can use several RenderFragment inside your component. Well, you can and it allows you to use your components like that
<Card Title="Special title treatment">
<Body>
<p class="card-text">With supporting text below as a natural lead-in to additional content.</p>
<a href="#" class="btn btn-primary">Go somewhere</a>
</Body>
<Footer>
2 days ago
</Footer>
</Card>
Please note
[Parameter(CaptureUnmatchedValues = true)]
public Dictionary<string, object> InputAttributes { get; set; }
= new Dictionary<string, object>();
This parameter is used on card to catch-all unmatched attributes that you want to assign to it <div class="card @Class" @attributes="InputAttributes">
. It's usually a good practice to include such catch-all property to every custom component. Saves some time on unmatched exceptions later. But be aware where you put it, for some components, it might be wise to apply attributes to the child or not apply it at all.
Progress Bar
Small component but I want to show it here as an example of the power of Blazor components and proper MVVM usage.
Progress Bar with MVVM Example
Let's dig into the example of usage of ProgressBar with backing it ViewModel.
View
<h2 class="mt-3">Interactive (with VM)</h2>
<ProgressBar Progress="@DataContext.Progress" Class="mt-2 mb-2">@((DataContext.Progress/100f).ToString("P0"))</ProgressBar>
<CommandButton class="btn-primary" Command="@DataContext.Add">+</CommandButton>
<CommandButton class="btn-secondary" Command="@DataContext.Subtract">-</CommandButton>
As you can see View is quite simple: we have progress which is transformed like that @((DataContext.Progress/100f).ToString("P0"))
for display only and couple buttons with commands Add
and Subtract
.
These actions are calculated in ViewModel. To ensure that ViewModel created, initialized, and linked I use ContextComponentBase as a base class for all my pages.
Page code-behind looks like this
public partial class Progresses
{
[Inject]
protected new ProgressViewModel DataContext
{
get => base.DataContext as ProgressViewModel;
set => base.DataContext = value;
}
}
Ensure that .razor
page itself is inherited from the proper class
@page "/progress"
@inherits Xakpc.BlazorBits.Bootstrap.ViewModels.ContextComponentBase;
That's all we need for View, let's switch to ViewModel!
ViewModel
First, check out the previous code snippet. Our ViewModel is Injected into our View - so let's not forget to add it in Startup.cs
services.AddScoped<ProgressViewModel>();
I recommend read several times about Scopes in Blazor. Once I lost a day trying to fix a problem caused by misunderstanding Blazor DI Scopes.
Finally a ViewModel itself
public class ProgressViewModel : ViewModelBase
{
private int _progress;
public ProgressViewModel()
{
Add = new Command<int>(i => Progress += 1, i => Progress < 100);
Subtract = new Command<int>(i => Progress -= 1, i => Progress > 0);
}
public int Progress
{
get => _progress;
set { _progress = value; OnPropertyChanged(nameof(Progress)); }
}
public override Task InitializeAsync()
{
Progress = 22;
return base.InitializeAsync();
}
public ICommand Add { get; set; }
public ICommand Subtract { get; set; }
}
I will not go deep into VM, only point to main things
-
InitializeAsync
called once per scope on first page-load - that means once per connection for Blazor Server because we added it withAddScope
; - Commands set
Progress
which fireOnPropertyChanged
which fireStateHasChanged
which fire page redraw; - Commands have
CanExecute
func to ensure that Progress in range 0-100; - CanExecute also used to disable buttons when needed;
Spinner
Spinner is another simple example of how easy to wrap into a Razor component.
Toasts
For toasts, I usually don't use a Bootstrap component but a nice Blazor port of very popular toastr.js
sotsera / sotsera.blazor.toaster
A Toastr.js port to Blazor in pure .Net.
Sotsera.Blazor.Toaster
A Blazor port of Toastr.js to Server and Webassembly Blazor Apps.
The transitions are implemented using System.Threading.Timer
timers so the resource usage should be closely monitored when using the server-side hosting model.
Only for server-side projects
The static assets from Razor Component Libraries are available by default only in Development mode. They can be enabled on Production using the UseStaticWebAssets()
method in the Program.cs
file as in the following example:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStaticWebAssets();
webBuilder.UseStartup<Startup>();
});
Sample
The client-side sample project has been published here.
Changes
__version 3.0.0
- new configuration options for styling the toast title (ToastTitleClass) and message (ToastTitleClass) and an example for aligning their text toβ¦
I should probably try Bootrstap toasts someday.
Modal
Bootstrap has a Modal component but it is highly dependant on JavaScript. In my work with Blazor, I prefer to minimize the amount of JS code overall, so I use Blazored.Modal
- an amazing native component for Modals. You should check it out.
And that's all for now. I am not very good at writing such long articles, so will be appreciated for any feedback.
And if you have any questions, feel free to mention me in Twitter
Top comments (0)