Go faster with your own re-usable Blazor components

June 30, 2020 · 8 minute read · Tags: blazor

Take a look at any web application these days and you’ll notice something…

No matter what the sector, design or purpose, the “modern web app” generally consists of a few key UI elements or components.

Even the most unusual, complex, visually interesting designs have some degree of consistent UI, with specific elements or components used and re-used throughout the application.

Take the BBC homepage for example.

BBC Homepage

Look at this with a view to identifying the key elements and they soon start to leap off the screen.

The obvious ones here are the stories, each with their own headline, image and category.

But we also have sections, and even things like the heading “Welcome to the BBC” (chances are these headings are consistent across the site).

There are probably more, but here are the key “elements”…

  • Heading
  • Date
  • Section
    • Story
      • Headline
      • Category
      • Image

Notice too how these are hierarchical in nature (so a section can contain one or more stories, each story includes a headline etc.)

This is a perfect fit for a “component-based” approach to building your web application, where each component is relatively straightforward in itself and can be used in lots of places to build your entire application (with as little effort and fuss as possible!)

Unleash Blazor's Potential

Blazor promises to make it much easier (and faster) to build modern, responsive web applications, using the tools you already know and understand.

Subscribe to my Practical ASP.NET Blazor newsletter and get instant access to the vault.

In there you'll find step-by-step tutorials, source code and videos to help you get up and running using Blazor's component model.

I respect your email privacy. Unsubscribe with one click.

    How to create re-usable components in Blazor

    So how do we tackle this with Blazor?

    How can we create our own re-usable components and use them to rapidly build an entire application?

    Let’s say, for arguments sake we’re building a simple “dashboard”.

    We want the dashboard to comprise several panels, something like this…

    Visually a panel is pretty straighforward.

    A couple of divs inside another div should do it!

    <div class="border-2 border-gray-600 m-4">
        <div class="bg-gray-700 text-white text-2xl p-2 font-bold text-center">
             Panel 1
        </div>
        <div class="p-6">
            This is a panel
        </div>
    </div>
    

    Run this in the browser and we have a panel.

    Make it re-usable

    So far so good, but is this really re-usable? Unless we want every panel to say “This is a panel”, probably not…

    We need a way to set this title and contents to different values for each panel.

    Let’s tackle the title first.

    Panel.razor

    <div class="border-2 border-gray-600 m-4">
        <div class="bg-gray-700 text-white text-2xl p-2 font-bold text-center">
             @Title
        </div>
        <div class="p-6">
            This is a panel
        </div>
    </div>
    
    @code {
        [Parameter]
        public string Title { get; set; }
    }
    

    Now we can set this title every time we render a panel.

    Index.razor

    <Panel Title="Still a panel"/>
    <Panel Title="Speak for yourself"/>
    

    And here’s the result…

    That’s better!

    Different content

    Now for the “contents” of the panel.

    Unlike the title, the contents are likely to require more than just some text.

    Imagine a real dashboard; panels could show graphs, tables, images and any number of other things.

    What we need, is a way to declare an instance of our component, put content in the “middle” of it, and have that rendered in the component itself…

    <Panel Title="Still a panel">
        <p>This really could be anything: table, image, graph...</p>
    </Panel>
    

    With this approach we can keep our consistent look and feel, but put virtually anything in the panel itself.

    Blazor handles this using something called a RenderFragment.

    Here’s how it works.

    <div class="border-2 border-gray-600 m-4">
        <div class="bg-gray-700 text-white text-2xl p-2 font-bold text-center">
            @Title
        </div>
        <div class="p-6">
            @ChildContent
        </div>
    </div>
    
    @code {
    
        [Parameter]
        public string Title { get; set; }
    
        [Parameter]
        public RenderFragment ChildContent { get; set; }
    
    }
    

    When we declare a property of type RenderFragment and call it ChildContent we tell Blazor to go ahead and automatically populate this with the contents we included when we declared an instance of our component.

    From here we can simply render these contents wherever we wish in our component using @ChildContent.

    Set styles from “the outside”

    Finally, let’s say we’re happy with this basic panel but sometimes we want it to have a red background, and white text…

    We need a way to specify our style preferences when we render the Panel…

    Panel.razor (code)

    @code {
    
        [Parameter]
        public string Title { get; set; }
    
        [Parameter]
        public RenderFragment ChildContent { get; set; }
    
        [Parameter]
        public string TitleStyle { get; set; }
    
    }
    

    Here we’ve exposed a TitleStyle parameter which expects a string (containing CSS classes).

    Now we need a way to render these extra classes in the div which dispays our panel’s title…

    Panel.razor (markup)

    <div class="@($"text-white text-2xl p-2 font-bold text-center {TitleStyle}")">
        @Title
    </div>
    

    This is a littly clunky, but breaking it down, we’ve essentially wrapped the entire existing class value in this…

    @()
    

    This makes it possible to use C# to declare an interpolated string…

    $"text-white text-2xl p-2 font-bold text-center {TitleStyle}"
    

    Which means we can append the value of TitleStyle to the existing hardcoded CSS classes.

    Now we can specify the style when we declare an instance of our Panel component.

    <Panel Title="Still a panel" TitleStyle="bg-red-600">
        <p>This really could be anything: table, image, graph...</p>
    </Panel>
    
    <Panel Title="Speak for yourself" />
    

    And when Blazor renders our two Panel instances, one will have a red background (because it’s important, so very important!)

    Now this works, but what if we want to render more than one of these “important” panels?

    We don’t really want to litter our code with repeated references to bg-red-600, not least because we might want to tweak that color, or add additional styles to it at a later date.

    This is where a little component composition goes a very long way.

    Compose those components

    The tempation is to somehow modify (or even duplicate) our existing Panel component to handle this requirement to have a re-usable “Important” panel.

    However, components offer us an alternative approach.

    We can create a more specialized version of Panel to use whenever we need an angry looking red panel.

    ImportantPanel.razor

    <Panel Title="@Title" TitleStyle="bg-red-600">
        @ChildContent
    </Panel>
    
    @code {
    
        [Parameter]
        public string Title { get; set; }
    
        [Parameter]
        public RenderFragment ChildContent { get; set; }
    
    }
    

    Note how this wraps the existing component and sets its TitleStyle property…

    Now we can use ImportantPanel and still specify a Title and ChildContent to render…

    Index.razor

    <ImportantPanel Title="Still a panel">
        <p>This really could be anything: table, image, graph...</p>
    </ImportantPanel>
    
    <Panel Title="Speak for yourself" />
    

    Are we done then?

    Not quite.

    I’m not sure about you, but I’m a little concerned about that ImportantPanel.

    It’s fine at the moment, but if we add additional parameters to Panel it feels like it’s going to be a bit of a hassle to go and update ImportantPanel to pass them along.

    It could get worse too, if we end up with lots more specialised panels, every one would need to be modified every time Panel exposes a new parameter.

    Say for example we added an Image parameter to Panel.

    We’d have to modify ImportantPanel and any other specialized panels to accept this Image parameter, even if they don’t need to do anything with it other than pass it along to the Panel.

    Multiple specialized panels

    Thankfully, there is an alternative.

    We can drastically simplify ImportantPanel by making it accept and forward any and all parameters we throw at it.

    Here’s how…

    ImportantPanel.razor

    <Panel @attributes="Attributes" TitleStyle="bg-red-600"/>
    
    @code {
    
        [Parameter(CaptureUnmatchedValues = true)]
        public Dictionary<string, object> Attributes { get; set; }
    
    }
    

    The Attributes dictionary is a Parameter decorated with the option to CaptureUnmatchedValues.

    This means when we declare an instance of ImportantPanel and specify parameters, any parameters not explicitly referenced in ImportantPanel will be gathered up into this dictionary.

    Then we just need a way to “splat” them on to our Panel component.

    That’s what @attributes="Attributes" does; it’s literally called “Attribute Splatting”.

    Now we have an ImportantPanel which should be a breeze to maintain. In fact, we only need to come back to and modify it if we decide that important panels need to behave differently, or we need to tweak its appearance.

    In Summary

    Once you get the hang of “thinking in components”, composing your application from lots of small, single-purpose components becomes a breeze.

    Use these Blazor features to keep your app on track:

    • Parameters (to pass values to your components)
    • ChildContent Parameters (to render the “contents” of your components)
    • CaptureUnmatchedValues (to capture any parameters you don’t need to explicitly handle)
    • Attribute splatting (to pass multiple attributes onto another component in one go)

    Join the Practical ASP.NET Newsletter

    Ship better Blazor apps, faster. One practical tip every Tuesday.

    I respect your email privacy. Unsubscribe with one click.

      Next up

      3 simple design tips to improve your Web UI
      Spruce up your features
      The quickest way to integrate PayPal checkout with Blazor SSR in .NET 8
      JavaScript Interop works differently with Blazor Server-side rendering
      Interactive what now? Deciphering Blazor’s web app project template options
      Create a new Blazor Web App and you’ll be asked how you want interactivity to work, but what does it all mean?