5

Your Blazor component needs data in a certain format, where to Map?

 1 year ago
source link: https://jonhilton.net/mapping/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Your Blazor component needs data in a certain format, where to Map?

April 3, 2023 · 4 minute read · Tags: blazor

Blazor components can accept parameters of virtually any type.

This makes for a simple option when you fetch data from a backend API/service as you can often use that data “as is” in your component.

For example, you might call an API to fetch product details which returns a list of items of type Product. The chances are you can just use that directly in your Blazor UI.

@foreach(var product in products) {
    <ProductDetails Product="@product"/>
}

In this example the ProductDetails component is specific to this page, and tightly coupled to the structure of the incoming Product data, so this feels like a sound approach.

Another option is to have your component accept primitives like string, or int, and send those in to the component.

<ProductDetails name="@product.Name" price="@product.Price" />

Now we could use ProductDetails anywhere we like, so long as we give it values for its Name and Price.

But, what if we have a truly re-usable component that needs to accept data in a certain format.

For example, let’s say we have a TreeView component which takes in a list of TreeItem objects.

TreeView.razor

[Parameter]
public List<TreeItem> Items { get; set; }

TreeItem.cs

public class TreeItem {

    public string Name { get; set; }
    
    public List<TreeItem> Children { get; set; }  

}

You can imagine this could be useful for showing nav items, or a folder tree, or anything else with a ‘Tree Like’ structure.

But now we’ve got a problem!

Every time we use this TreeView component, we need to map the data we’re trying to use (often from a backend API or service) to a list of TreeItem.

For example, we might pull a list of folders from a server and want to render those as a Tree.

FolderView.razor

<TreeView Items="?"/>
@code {

    protected override void OnInitialized() {
        var folders = _backendAPI.ListFolders();
        // map folders to items of type `TreeItem`
    }
    
}  

So where do we put the mapping code?

Well, one option in this case is to write the mapping code directly in the component (FolderView in this case):

FolderView.razor

<TreeView Items="treeItems"/>
@code {

    List<TreeItem> treeItems = new();

    protected override void OnInitialized() {
        var folders = _backendAPI.ListFolders();
        
        foreach(var folder in folders){
            treeItems.Add(new TreeItem { Name= folder.Name });
            // possibly map sub folders here as well
        }
    }

}  

This is probably fine if the mapping code is simple/minimal (as above).

But if you’re mapping between objects with more properties, and perhaps a bit more logic to ensure the correct data comes out the other side, this can turn into a lot of messy code.

So where else could it go?

Enter Extension Methods - An Alternative

This is where I quite often turn to extension methods.

You can create a separate method (in a separate class, in a separate file) which has the sole job of mapping data from your backend type to the structure needed for your component.

public static class FolderExtensionMethods
{
    public static IEnumerable<TreeItem> ToTreeItems(this List<Api.Folder> folders)
    {
        List<TreeItem> treeItems = new List<TreeItem>();

        foreach (var folder in folders)
        {
            treeItems.Add(new TreeItem { Name = folder.Name });
            // possibly map sub folders here as well
        }

        return treeItems;
    }
}

Or, for bonus points, you can use yield return here for a more succinct version 🎯

public static class FolderExtensionMethods
{
    public static IEnumerable<TreeItem> ToTreeItems(this List<Api.Folder> folders)
    {
        foreach (var folder in folders)
        {
            // additional mapping goes here
            yield return new TreeItem { Name = folder.Name };           
        }        
    }
}

Either way, you can use this extension method in your component, and your data is successfully mapped!

FolderView.razor

@using FolderExtensionMethods;
<TreeView Items="treeItems"/>
@code {

     IEnumerable<TreeItem> treeItems;

    protected override void OnInitialized()
    {
        var folders = _backendAPI.ListFolders();
        treeItems = _folders.ToTreeItems();
    }
    
}  

Your component remains easy to read, understand and maintain, plus you’ve encapsulated all that unsightly mapping code into a separate method which you can invoke at will.

Next up

Are you sure you need that ‘else’ - Extend your Blazor components

Blazor’s component model can make you a lot more productive, if you let it…

Blazor United - Solving Blazor’s biggest challenges?

Blazor Server and WASM both have trade-offs, Blazor United looks set to tackle those and make Blazor a viable framework for all your web development needs.

Use a recursive Blazor component to render a TreeView

How to render nested “nodes” in a TreeView

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK