Evolution of Web Browser User Interface Frameworks

September 02, 2021 in Web Development Articles

Written by Eric Greene


Since the creation of the World Wide Web, the delivery of information to people has been transformed by web technologies such as web servers, web browsers, and web pages. The capabilities and uses of these technologies have evolved greatly over the decades. In particular, the evolution of the web page has greatly impacted users and developers alike. In the last decade, users and developers have seen the rise of Single-Page Applications (SPA). SPA user interfaces (UI) and their associated libraries and frameworks have become very popular and have transformed both the user and developer web page experience.

Gone are the days of server-side web applications where UIs were dynamically generated on the server and sent as HTML to the web browser. The constant need to serve a new page for every DOM (document object model) change did not provide a great user experience and it placed an unnecessary load on network and server resources.

To improve performance and user experience, we must shift UI rendering from the server to the web browser client. This reduces network and server load, avoids page reloading, and makes changes to the DOM tree through the browser's programming language: JavaScript.

Around 10 years ago, several improvements in web browser technology appeared. Web browser APIs became more standardized. JavaScript began to mature with ES5 and a plethora of JavaScript UI frameworks came into common usage. With the improvements in browser technology, developers began to leave the comfort of statically-typed and strongly-typed languages such as Java and C# for JavaScript in the web browser. Why JavaScript? It is the only language supported in all web browsers.

In the early days of this transition from server to client UI rendering, libraries such as jQuery, Backbone.js, Angular.js, and Dojo ruled the development world. While they worked, the resulting code was often difficult to maintain. Furthermore, the efficiency of DOM updates was limited to the skill of the individual developer.

To make things better, component-based libraries entered the scene. These new component-based libraries, such as React and Angular, provided two huge benefits.
First, they provided a good structure for creating reusable UI parts and they provided good patterns and practices for composing those UI parts to build a complete web page. Second, these component-based systems were declarative. Declarative components meant the developer did not write the code to update the DOM tree directly. DOM tree updates would be handled by the component library. One such approach to a library updating the DOM is React's "Virtual DOM" mechanism.

TypeScript, Better Typing for JavaScript

Even with the rise of component-based JavaScript libraries/frameworks, one problem remained for many developers, code had to be written in JavaScript. To help mediate this problem, Microsoft created TypeScript. TypeScript is a superset of the JavaScript language that adds static typing and strong typing to JavaScript's dynamic typing and loose typing. With the proper type definition packages, TypeScript can be used with any JavaScript library to write statically and strongly typed code. The usage of TypeScript is a real help for enterprise-class projects. Typically, enterprise-class projects have many developers, each of whom works on specific parts of the application. Static typing helps developers understand the overall structure of the project and ensures they work correctly with parts of the project they did not code. The downside to TypeScript is that there are incompatibilities between versions (including minor release versions) and it results in a lot of extra code, some of which can be hard to write.

For libraries such as React, both JavaScript and TypeScript can be used. While JavaScript is more common, TypeScript has a strong following in the React community. For modern Angular (not to be confused with the older Angular.js), TypeScript is a requirement. Angular leverages the additional capabilities of TypeScript as part of its framework. A good example of Angular's use of TypeScript features is the use of decorators to attach metadata to class definitions. The metadata is used to describe the role of a class within the Angular application such as a class being a component or pipe.

Statically-typed,  Strongly-typed,  Browser-based Applications

For many years, these JavaScript/TypeScript libraries and frameworks have dominated the web browser UI development landscape. Nevertheless, the long-sought desire of developers has been the ability to write statically-typed, strongly-typed, browser-based applications without using JavaScript. In the early days, this was accomplished with Java Applets, followed by Flash, and for a brief period, Silverlight. Each of these browser-based technologies was proprietary and usually required an extension to be installed in the browser. The problem was the many browsers did not equally support the technologies. Often there were security issues with the technologies and large corporate enterprises would not let the extensions be installed. Additionally, private consumers did not know how to install the extensions, either. In the end, hardware/software vendors such as Apple would not support them on mobile devices.

Enter WebAssembly

WebAssembly achieves part of the goal sought by technologies such as Java Applets, Flash, and Silverlight: running statically-typed, strongly-typed binary code directly in a web browser. WebAssembly is a standard and has been supported in all major browsers in 2017. Today, 94% of all browsers support it directly and for those older browsers that do not, it can be poly-filled with a library named "asm.js". Using languages such as C, C++, Rust, GoLang, and many others, code can be compiled to a WebAssembly binary, downloaded into the browser, and executed. Sounds awesome right? Yes, it is.

But what is the part of the goal that was not achieved? Unlike Java Applets, Flash and Silverlight, WebAssembly cannot directly interact with the UI, in this case, the DOM tree. It can interact with JavaScript, but not the DOM tree directly. So to use WebAssembly, there needs to be JavaScript code to marshal changes to the DOM on behalf of WebAssembly.

.NET and C#

Sure, writing WebAssembly code in C or C++ is cool, but who wants to write a web application with C? Can anyone remember the mid-90's and writing CGI programs in C? Even then, developers preferred Perl or PHP for writing web page code in C. Ideally, business application developers want to avoid JavaScript and write the code in a language they know, namely languages such as Java or C#. So it is possible to write C# code that runs as WebAssembly in the web browser? The answer is 'yes', it's called Blazor WebAssembly. Blazor is a .NET and C# based framework for writing SPA web applications. It comes in two flavors (hosting models): Server and WebAssembly. The focus for this blog post is WebAssembly.

Blazor

Like React and Angular, Blazor WebAssembly uses a component-based pattern to promote reusability and good UI design patterns. It is declarative, the developer is not writing code to directly modify the DOM tree. The difference is that JavaScript is not used. Instead, a special version of .NET created with Mono runs as WebAssembly in the web browser. Mono is a cross platform, open-source .NET framework sponsored by Microsoft. This version of .NET downloads the DLLs produced by the C# project and runs the C# code in the browser. For DOM updates, Blazor provides a JavaScript file that the WebAssembly code interacts with to perform the DOM updates. This allows developers to mostly use C# to write UI code (just like they did for older server-side applications), but run the code directly in the web browser like the new SPA frameworks such as React and Angular. Truly the best of both worlds. A great user and developer experience.

Blazor WebAssembly in the Real World

Does it work well? The answer is mostly yes. The only challenge with Blazor WebAssembly is the initial load of the web page. Because .NET and other DLLs need to be downloaded, there can be a slight delay in the initial load. While this can be worked around to a limited degree, the delay is still noticeable. In truth, React and Angular applications can experience a similar delay, but this is handled by the server-side rendering of the first page. The Blazor team is working on adding a pre-rendering mechanism to Blazor WebAssembly. Except for this issue, it works well, especially for traditional business web application development.

Code Comparison

Before wrapping up this blog post, let's take a look at a component coded in React, Angular, and Blazor.

React

Here is an ItemList component in React:

import PropTypes from 'prop-types'; 

export const ItemList = props => {

  const itemAction = itemValue => { 
    props.onItemAction(itemValue);
  };

return (
  <ul> 
    {props.items.map(item => <li key={item.value}> 
      {item.text} 
      <button type="button" onClick={}>{props.actionText}</button>
    </li>)} 
  </ul> 
  ) 

}; 

ItemList.defaultProps = {
  items: [],
  itemAction: "Select", 
};

ItemList.propTypes = {
  items: PropTypes.arrayOf(PropTypes.shape({
    value: PropTypes.oneOfType[PropTypes.string,PropTypes.number], 
    text: PropTypes.string,
  })), 
  actionText: PropTypes.string,
  onItemAction: PropTypes.func.isRequired,
}; 

 

The component is coded with a JavaScript function. The parameters, called props, are passed as a parameter to the function. The function contains both the code and template. In React, templates are implemented with an HTML-like syntax called JSX. Under the hood, JSX is nothing more than function calls to create elements that are ultimately rendered in the DOM tree. Data to be displayed in the component is passed in via props.

For dynamic parts of the template, plain JavaScript expressions are placed inside curly braces within the JSX. In the above code, the map function is used to transform the array of item objects into an array list item elements populated with the item object. Plain JavaScript such as this makes React easier to work with in many cases.

To communicate data from the child component to the parent component, an event handler pattern is followed. The parent component passes a function object reference via props to the child component. Then the child component invokes the function, passing whatever data is desired into the function, which is then utilized by the parent. In the above example, when the user clicks the action button, the list sends to the parent component the value of the item whose button was clicked.

The ItemList component would be called like this:

<ItemList items={someItems} actionText="Delete" onItemAction={deleteItem} 
/>

 

The ItemList component is called with JSX and the parameters are passed as attributes, which is  similar to how HTML works. The ItemList element itself is not rendered to the DOM tree.

Angular

Here is the same ItemList component implemented in Angular:

export type Item = {
  value: number,
  text: string, 
}; 

 

Angular uses TypeScript. So unlike React with JavaScript, all of the model types must be defined before they can be used. In the above code, a type alias is used to define the Item type.

Next is the string-based template of the component:

<ul>
  <li *ngFor="let item of items">
    {{item.text}} 
    <button (click)="doItemAction(item.value)">{{actionText}}</button>
  </li>
</ul> 

 

The template is an HTML string with special DOM markers called directives applied to it. This HTML string is processed by the Angular compiler, which applies logic to the template through the directives. For example, the ngFor directive will duplicate a tree of HTML based upon the elements in a collection of data.

With Angular, the template of the component and the code of the template are separated into two different files. Each component in Angular is a combination of a class and a template. While most components in React are created with plain functions, historically, classes could be used as well.

import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; 
import { Item } from '../../models/item'; @Component({ selector: 'app-item-list', templateUrl: './item-list.component.html', styleUrls: ['./item-list.component.css'] }) export class ItemListComponent implements OnInit { @Input() items: Item[] = []; @Input() actionText = "Select"; @Output() itemAction = new EventEmitter<number>(); constructor() { } ngOnInit(): void { } doItemAction(itemValue: number) { this.itemAction.emit(itemValue); } }

 

When a component is used, a new class instance is created. Observe that the class has a decorator named Component. This decorator applies metadata to the class definition to tell Angular that this class is a component. Classes be used for other Angular parts such as modules, pipes, and services.

The Input decorated properties are the parameters of the component. They are set after the component is instantiated and before it is rendered. Angular components output data to their parents through Output decorated properties. Outputs are coded as EventEmitters and use a technology called RxJS to output data.

<app-item-list [items]="someItems" actionText="Delete"
(itemAction)="deleteItem($event)"></app-item-list> 

 

Using the CSS selector pattern from the component's metadata, a custom HTML element is used to display the component. With Angular, the custom element is rendered to the DOM tree.

The element attributes will take one of three forms. The attribute with no square brackets and no parentheses assigns the literal string value to the component's input property. The attribute with the square brackets evaluates the attribute value as an Angular expression, then assigns the result of the evaluation to the component's input property. The attribute with the parentheses subscribes to the output property's event emitter to receive data from the child component.

Blazor (C# and Razor)

Blazor uses Razor Components to display its UI. The name Razor comes from the Razor syntax used in traditional server-side ASP.NET MVC applications. The Razor syntax is used for components, too. Like Angular, Razor Components are coded with a statically typed language, C#, so the model has to be defined.

namespace ToolsApp.Models 
{ 
  public class Item 
  { 
    public int Value { get; set; }
    public string Text { get; set; }
  } 
} 

Like React, the template and code for the Razor Component are stored in the same file. The string- based template is at the top and the code block is specified underneath. Like Angular's Input and Output decorators for parameters, all parameters for Razor Components are decorated with the Parameter attribute. Plain data parameters can be of any type. Parameters used to output data to the parent are typed as an EventCallback.

@using ToolsApp.Models; 

<ul> 
  @foreach (var item in Items) 
  { 
    <li> 
      @item.text 
      <button type="button" 
        @onclick="@(() => itemAction(item.value))">@ActionText</button> 
    </li> 
  } 
  </ul>

@code { 

  [Parameter]
  public IEnumerable<Item> Items { get; set; } = new List<Item>(); 

  [Parameter] 
  public string ActionText { get; set; } = "Select"; 

  [Parameter] 
  public EventCallback<int> OnItemAction { get; set; }
  private void itemAction(int itemValue)) 
  { 
    OnItemAction.InvokeAsync(itemValue) 
  }
}

 

<ItemList Items="@items" ActionText="Delete" OnItemAction="deleteItem" /> 

 

Like React, the component is called by name using HTML, but the component element itself is not rendered to the DOM tree. Parameters are passed via the attributes. Some attribute values will contain an @ symbol indicating that the value is a Razor expression that is evaluated first, and then the result is passed in.

Compare and Contrast

All three examples above are components. Their implementation syntax greatly varies, but the code that calls the components appears quite similar. There are implementation details that would reveal many more similarities and differences, but let's focus on what they have in common.

Template

For the component template, all three frameworks provide a place for an HTML-like syntax to design the UI. The templates support both normal HTML as well as code to populate the HTML with dynamic values.

React templates are coded with an HTML-like syntax called JSX. React templates are not string-based. The JSX is an alternative syntax for making a function call. So React templates are actually just JavaScript function calls where the element type and props are passed in. Angular uses string-based templates that are compiled with the Angular compiler to produce a view class. This view class is used by Angular to generate the UI. The Blazor template is compiled Razor syntax.

Input Parameters

All three support a parameterization mechanism. In React, parameters are referred to as props and are passed as a single object into the component function. Each property of the object is an attribute key-value pair. In Angular, parameters are referred to as inputs and outputs, and they are properties on the component instance. Once the instance is created, Angular assigns data to the input parameter properties. Blazor's Razor components work similar to  Angular's class-based components.  While there is no class keyword in the Razor Component file, when the Razor code is compiled, a component class is created.  Also, the properties decorated with the Parameter attribute will receive the parameter values, which is similar to how the Input decorator works in Angular.

Output Event

An important ability of components is outputting data to a parent component via an event handler. All three frameworks support this ability. For React, a child component transmits data to its parent by invoking a function passed from the parent component to the child component via props. For Angular, an Output property is defined as an EventEmitter. An Event Emitter is essentially an RxJS Subject that is used to publish events that are subscribed to by the parent component. The Blazor syntax is a mix of the React and Angular syntax. The function is passed as a parameter, but the parameter has a type of EventCallback. The child component outputs data through Event Callback through the InvokeAsync method.

Conclusion

A big change in UI development many years ago was the adoption of the component-based pattern seen in frameworks such as React, Angular, and Blazor. For many years, client-side components were only available through JavaScript (and later TypeScript) UI frameworks. However, with the release of WebAssembly, popular languages such as C# can be used to build client-side components. Blazor enables server-side .NET developers to benefit from reusability and composability of components with the power of the statically and strongly-typed C# language. Even better, Blazor components are very similar to React and Angular components in both form and function. This similarity enables existing React and Angular developers to easily transition to this new and exciting technology.

Acclebrate offers private, customized Web Development courses delivered at your site or online for your team of 3 or more attendees.


Written by Eric Greene

Eric Greene

Eric is a professional software developer specializing in HTML, CSS, and JavaScript technologies. He has been developing software and delivering training classes for nearly 19 years. He holds the MCSD Certification for ASP.Net Web Applications, and is a Microsoft Certified Trainer.


Learn faster

Our live, instructor-led lectures are far more effective than pre-recorded classes

Satisfaction guarantee

If your team is not 100% satisfied with your training, we do what's necessary to make it right

Learn online from anywhere

Whether you are at home or in the office, we make learning interactive and engaging

Multiple Payment Options

We accept check, ACH/EFT, major credit cards, and most purchase orders



Recent Training Locations

Alabama

Birmingham

Huntsville

Montgomery

Alaska

Anchorage

Arizona

Phoenix

Tucson

Arkansas

Fayetteville

Little Rock

California

Los Angeles

Oakland

Orange County

Sacramento

San Diego

San Francisco

San Jose

Colorado

Boulder

Colorado Springs

Denver

Connecticut

Hartford

DC

Washington

Florida

Fort Lauderdale

Jacksonville

Miami

Orlando

Tampa

Georgia

Atlanta

Augusta

Savannah

Hawaii

Honolulu

Idaho

Boise

Illinois

Chicago

Indiana

Indianapolis

Iowa

Cedar Rapids

Des Moines

Kansas

Wichita

Kentucky

Lexington

Louisville

Louisiana

New Orleans

Maine

Portland

Maryland

Annapolis

Baltimore

Frederick

Hagerstown

Massachusetts

Boston

Cambridge

Springfield

Michigan

Ann Arbor

Detroit

Grand Rapids

Minnesota

Minneapolis

Saint Paul

Mississippi

Jackson

Missouri

Kansas City

St. Louis

Nebraska

Lincoln

Omaha

Nevada

Las Vegas

Reno

New Jersey

Princeton

New Mexico

Albuquerque

New York

Albany

Buffalo

New York City

White Plains

North Carolina

Charlotte

Durham

Raleigh

Ohio

Akron

Canton

Cincinnati

Cleveland

Columbus

Dayton

Oklahoma

Oklahoma City

Tulsa

Oregon

Portland

Pennsylvania

Philadelphia

Pittsburgh

Rhode Island

Providence

South Carolina

Charleston

Columbia

Greenville

Tennessee

Knoxville

Memphis

Nashville

Texas

Austin

Dallas

El Paso

Houston

San Antonio

Utah

Salt Lake City

Virginia

Alexandria

Arlington

Norfolk

Richmond

Washington

Seattle

Tacoma

West Virginia

Charleston

Wisconsin

Madison

Milwaukee

Alberta

Calgary

Edmonton

British Columbia

Vancouver

Manitoba

Winnipeg

Nova Scotia

Halifax

Ontario

Ottawa

Toronto

Quebec

Montreal

Puerto Rico

San Juan