Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Comparing Native Blazor Components to Wrapped JavaScript Components

DZone 's Guide to

Comparing Native Blazor Components to Wrapped JavaScript Components

Learn how Blazor works under the hood.

· Web Dev Zone ·
Free Resource

Should we rewrite native UI components or reuse existing JavaScript UI components? We compare native Blazor components to wrapped JavaScript components by understanding how Blazor works, including what native vs. interop means and more.

Blazor is a new Single Page Application (SPA) framework that utilizes WebAssembly to run .NET application code in the browser. This future-forward framework allows developers to leverage their existing .NET code and skills to create web applications that can run completely client-side without the need for browser plugins. As with any new web framework, we're presented with a challenging decision of bringing along assets from previous works into the new system.

In the case of Blazor, the challenge presents itself in the UI components of the application model. Due to previous lack of choice, web UIs are written in JavaScript; whereas Blazor heavily utilizes C# and Razor markup syntax. This stark contrast of choices forces one's hand in one of two directions - rewrite native UI components or reuse your JavaScript UI components.

Let's explore the challenge by understanding how Blazor works, what native vs. interop means, all while discussing the tradeoffs of choosing between the two approaches.

Blazor Architecture Basics

Blazor is a new breed of web application framework. Blazor is similar in many respects to React or Angular, but what sets it apart is the underlying architecture is crafted upon WebAssembly instead of JavaScript.

WebAssembly (WASM) is a web standard, developed by the World Wide Web Consortium (W3C), which defines an assembly-like binary code format for execution in web pages. WebAssembly is the cornerstone of Blazor in that it provides a platform for the Mono Runtime for WebAssembly, a .NET runtime compiled to WASM format.

A Blazor application is a true .NET application, running on a .NET runtime inside the browser. This is quite an important factor in the decision process when it comes to writing UI components for Blazor, as you should know the context in which the component is executed.

Blazor Architecture: DOM Abstraction

Much like its JavaScript siblings, Angular and React, Blazor employs a similar approach to handling changes to the Document Object Model (DOM). No matter what framework you choose, DOM manipulation is a taxing process that is often compounded by directly changing its structure more frequently than necessary. Without a proper execution plan, most approaches to DOM manipulation destroy and rebuild chunks of DOM, ripping out multiple nodes and repainting them. This is solved in modern frameworks through the use of a DOM abstraction.

The DOM abstraction in Blazor is called the RenderTree. It's a lightweight representation of the DOM. Think of the RenderTree as a copy where changes can be quickly made as nodes in this tree can be created, updated, and deleted without re-rendering the page. Now, multiple components in the system can make changes against the RenderTree at once with much less of a performance hit. When the dust has settled, the RenderTree and DOM are reconciled by looking for differences between the two and re-rendering only what is absolutely necessary.

The RenderTree is vital to UI component behavior and render speed and therefore is important to consider when choosing between how to write UI components, especially when it comes to JavaScript.

Blazor Architecture: Native Components

In a Blazor application, components (.razor) are actually processed quite differently from traditional Razor (.cshtml) markup. Razor in the context of MVC or Razor Pages is processed directly to HTML, which is rendered server-side and sent over an HTTP request. A component in Blazor takes a different approach — its markup is used to generate a .NET class that builds the RenderTree.

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" onclick="@IncrementCount">Click me</button>


Each HTML element in the component is passed to the RenderTreeBuilder and given a sequence number used to quickly differentiate changes in the DOM.

    public class Counter : ComponentBase
    {
        protected override void BuildRenderTree(RenderTreeBuilder builder)
        {
            base.BuildRenderTree(builder);
            builder.AddMarkupContent(0, "<h1>Counter</h1>\r\n\r\n");
            builder.OpenElement(1, "p");
            builder.AddContent(2, "Current count: ");
            builder.AddContent(3, currentCount);
            builder.CloseElement();
            builder.AddContent(4, "\r\n\r\n");
            builder.OpenElement(5, "button");
            builder.AddAttribute(6, "class", "btn btn-primary");
            builder.AddAttribute(7, "onclick", M...<UIMouseEventArgs>(this, IncrementCount));
            builder.AddContent(8, "Click me");
            builder.CloseElement();
        }


This component architecture is fundamental to Blazor's operation and supports features built into the framework such as component life cycle methods, templates, and validation.

Blazor Architecture: JavaScript Wrappers

A "native" Blazor component is one that is written using the framework's component architecture. The alternative is to create a wrapper for preexisting JavaScript components. When using JavaScript, a component is created that exposes a set of properties and methods that map to a JavaScript implementation. On the surface, a JavaScript-based component is used as a native component, but under the surface, it bypasses the RenderTree and the HTML is rendered by directly manipulating the DOM.

The Blazor RenderTree process vs. direct DOM manipulation.


The ability to perform calls to JavaScript from within a Blazor application is called the JavaScript interop. The interop is a necessary feature of Blazor, as it bridges any gaps between WebAssembly and DOM APIs. This is especially useful for features not supported in Blazor such as GeoLocation, File Access, and Camera APIs. This is a very powerful tool, but it can easily become bad practice, as it can circumvent the Blazor virtual DOM.

Leaky Abstractions

In software development, a leaky abstraction is an abstraction that leaks details that are supposed to abstract away. Wrappers have a natural tendency to fall victim to this. Because wrappers need support from outside the Blazor framework, they require additional details like id and ref attributes. Legacy JavaScript code relies heavily on ids and elementRefs. Since they exist outside of the RenderTree, they need to be located in the DOM by traversing the DOM structure and then manipulated — a costly routine.

<AcmeWidget ID="myThing" ref="myReference" ...>


Content and Template Support

Components are the building blocks of Blazor application. Components can host other components in two ways — through Child Components and Templates — and as a result, Blazor development is very nimble. Child Components and Templates are component properties with a special class called a RenderFragment (a delegate that writes the content to a RenderTreeBuilder). This is another oversight for components built from JavaScript wrappers and a prime reason to build native components.

Child components are extremely powerful as they can transform component behavior or composition of components. Such a feature can truly be appreciated by example. In the following code, a TelerikTabStrip is given a list of Forecastobjects. The list is iterated over using a simple foreach, building the tabs dynamically.

<TelerikTabStrip>
    @foreach (var f in Forecasts)
    {
        <TelerikTab Title=@f.City>
            <Weather City=@f.City
                     TempC=@f.Temp
                     Forecast=@f.Outlook>
            </Weather>
        </TelerikTab>
    }
</TelerikTabStrip>


Blazor framework's component architecture and use of RenderFragments allows Blazor to declare components in this way.

Dynamic components aren't limited to simple foreach loops either. Virtually any C# methodology can be applied to control rendering. In the next example, the same TelerikTabStrip component is used with an if statement, which is bound to a check-box embedded within one of the child tabs. Changing the value of the check-box in this instance instantly affects the visibility of the first tab.

<TelerikTabStrip>
    @if (showFirstTab)
    {
        <TelerikTab Title="First Tab">
            This is the first tab.
        </TelerikTab>
    }
    <TelerikTab Title="Second Tab">
        <label>
            <input type="checkbox" bind=@showFirstTab />
            Toggle first tab
        </label>
    </TelerikTab>
</TelerikTabStrip>


This is possible because the scope of showFirstTab is outside of the component itself. Since the components are native they obey Blazor's rendering and data binding capabilities.

More advanced scenarios play out when Templates are used to allow for customization of how a component renders markup. Templates can be used for simple tasks like formatting values or displaying images, while more extensive Templates can transform the user interface completely, adding entirely new functionality to a component. For further reading on this subject, the article "Why Blazor Grid Templates Will Make You Question Everything" gives a comprehensive overview with code examples.

Pre-Rendering

Blazor isn't just capable of running client-side; it can be hosted server-side as well. When Blazor runs server-side it has the ability to pre-render views to eliminate loading screens and enhance SEO. The way pre-rendering works in Blazor is similar to how ASP.NET Razor views and pages are rendered. In fact, because of this Blazor components are compatible with ASP.NET Razor views and pages. When using JavaScript-based components, the server can not pre-render the component as JavaScript is not yet available. Instead, the page will need to be rendered by JavaScript when it is parsed by the client. It's important to be aware that this mode of operation is unavailable when wrapping existing JavaScript components.

Validation

Validation in Blazor is tightly coupled with the core component system. Since Blazor uses C# throughout the programming model, it supports the popular DataAnnotation method of defining validation.

using System.ComponentModel.DataAnnotations;

public class ExampleModel
{
    [Required]
    [StringLength(10, ErrorMessage = "Name is too long.")]
    public string Name { get; set; }
}


The DataAnnotations can be used to trigger validation within business logic on the server and also UI validation. The built-in EditForm component provides validation and event handlers that provide a seamless work-flow in the component model.

<EditForm Model="@exampleModel" OnValidSubmit="@HandleValidSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <InputText id="name" bind-Value="@exampleModel.Name" />

    <button type="submit">Submit</button>
</EditForm>

@functions {
    private ExampleModel exampleModel = new ExampleModel();

    private void HandleValidSubmit()
    {
        Console.WriteLine("OnValidSubmit");
    }
}


Once again, this is a scenario that plays out best when components are built using native components. Native components share a state called an EditContext that allows components within an EditForm to communicate. Native components that have integrated the EditContext automatically validate when placed within an EditForm without additional code. When trying to wrap existing JavaScript components, validation requires leaky abstractions through the use of Id and ElementRef attributes.

Is JavaScript all Bad?

There is absolutely nothing wrong with using JavaScript libraries to support Blazor components. There are some use cases where JavaScript interop is a necessity to fill gaps as WebAssembly and Blazor are maturing products. JavaScript interop can be beneficial when it is used to augment native components by adding features that do not disturb the RenderTree, such as giving focus or providing keyboard navigation. In addition, JavaScript libraries can be used to add system-level functionality when tapping into DOM Web APIs like GeoLocation, File access, Canvas, and SVG.

Telerik Is Native First

With Telerik UI for Blazor we have taken a native-first development approach to building UI components. Best practice is to take native implementation as far as the framework allows, leveraging the RenderTree and allowing components to naturally hook into templates and validation. Our components make minimal use of JavaScript interop, ensuring that there are no leaky abstractions filtering up to the surface and getting in the customer's way. We have found extensive benefits with templates that offer an open-ended approach to web building applications.

Topics:
web dev ,blazor ,dom ,javascript ,leaky abstraction

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}