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.
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.
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.
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
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 with AddScope;
Commands set Progress which fire OnPropertyChanged which fire StateHasChanged 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
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:
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.