Under the hood of the frontend trilemma Series I: Commonalities


“Which frontend framework should I use for my project?”. This is a frequent topic for discussion among frontend folks and there are a plethora of online articles about it. However the comparison presented in these articles is only focusing on the differences among them. Rarely and not thoroughly it is described what is the common glue between these. Their common purpose is known but essential in understanding the differences between them is to first delve more into their shared concepts. Purpose of this article is to present the abstracted commonalities and underlying principles between Angular, React and Vue and to relate web components -a native web technology- with them. This post is part of a series of articles about the comparison of the aforementioned frameworks. Next articles tackle the differences between them in a technically deep way, which is unique and differs from the high-level comparison presented on the existing online resources.

Common Purpose: Building reactive user interfaces

Why do we need a modern frontend framework? The obvious answer might be to build web applications and more precisely their UI in a fast, secure and cross-browser compatible way. Let’s give an answer with an extra flavor: All of the three frameworks are about building reactive user interfaces. The special keyword here is reactive. A reactive user interface, as is defined in wikipedia, is an interface in which a user is immediately aware of the effect of its “gesture” (keystrokes, mouse clicks), of the state of its data (i.e. if they are saved) and knows how to get help. In other words, in a reactive design the system is responsive and reacting to the user regardless of the network connectivity or the server status. This definition is scoped around the “user experience”. Another, more technical dimension to reactive interfaces is given from, Raph Levien, according to whom reactive UI is a pipeline of tree transformations. A typical pipeline consists of the transform from the app state to the view tree (in Javascript is the DOM tree), from the view tree to a render tree, and from the render tree to drawing graphics primitives. To connect the dots with the declarative nature of the 3 frameworks (which is described in a later section), another definition that is given for reactive Interfaces is that they are a declarative way of expressing an app’s visual data.

Component Based Architecture

All three frameworks are based on component based architecture, meaning that reusable units, known as components are used to build reactive user interfaces. This is opposed to older monolithic approaches, where the construction was done at a page rather than at a component level. Every page was built independently of the other and there was duplication of code if a similar UI control or data was needed.

However the current, modern construction approach is different and similar to legos: building blocks of various sizes such as buttons, combo boxes, forms, and even greater UI constructs (tailored to each app’s needs such as course modules, or workflows tables) are combinedly used to build the interface. The skin of these reusable building blocks is usually defined in a shared UI Library. The shared UI library can be a product of the company itself or an external one, depending on the size of the company. In another article we will elaborate more on such UI libraries, how they are constructed (how much skin and how much business logic is shared) and how they are integrated in the frontend applications.

Then the building blocks are referenced from there and can be reused in many places within an app. Except for reusable skin these components contain properties, event handlers and various methods, so they represent well-defined communication interfaces. For example, a button can contain properties about its skin: color, size as well as properties for its event handlers, meaning what will happen onClick of this button, or onHover. When this button is used in various places its properties are initialized with different properties values each time. The decomposition of the design in these blocks is done based on logic and functionality. In the next diagram you can see how reusable components can be picked up from the shared UI Library.

Component-based Architecture: Button reusability

Declarative Rendering

Depending on how a user interface is constructed it can be classified into imperative or declarative one. All three frameworks are using declarative templates to compose the reactive interfaces. This means code is written in such a way so that html-like templates contain what should be rendered on the screen. Within these templates some parts are dynamic and some static. The dynamic parts contain the data and the static contain the layout and the aesthetics. All data is stored in a concept called state, which is a global javascript object that holds information about the whole app. The data in turn are pulled from the state down to the html-like (=declarative) template.

Whenever the data of the app changes they are pulled and the rendering of the respective DOM element on a specific html (declarative) template is automatically updated. The rendering technology declares how the state is translated into a DOM element. The UI engineer is not in charge any more to manually maintain this update, but this rendering technology determines what it needs to do in order to display that data correctly.

On the other hand, the imperative interfaces are constructed on templates like in the jQuery technology. In this technology manual DOM manipulation is done via methods of a well-specified interface (like appendChild, removeChild, setAttribute). Instead of displaying “what” will be rendered on the screen we code “how” data will be rendered. This is done in jQuery via targeting first specific html elements and then applying the aforementioned DOM operations. In imperative rendering many different functions push information down to an element. In this article you can read more about the distinction between these two.

Declarative Interfaces — Decoupling View from Data Logic

In the code examples below you can see clearly the difference between the “imperative way of jQuery” first and the “declarative templates” of the three frameworks. The same example is used for all code snippets to make the comparison easier.

jQuery (imperative)


<!DOCTYPE html>

<html>

  <head>

    <script src="https://code.jquery.com/jquery-3.5.0.js"></script>

    <script>

      function appendText() {

        var txt = document.createElement("p");

        txt.innerHTML = "Hello World";

        $("body").append(txt);

      }

    </script>

  </head>

  <body>

    <h1>Welcome to this App</h1>

    <button onclick="appendText()">Show Text</button>

  </body>

</html>

Vuejs (declarative)


<template>

  <div class="App">

    <h1> Welcome to this App </h1>

    Sample Item Name

    <button @click="onClickButton"></button>

    <p v-if="buttonPressed"> Hello World </p>

  </div>

</template>

<script lang="ts">

  import Vue from 'vue'

  import { Component, Prop } from 'vue-property-decorator'

  @Component

  export default class App extends Vue {

    @Prop() item!: person

    buttonPressed: boolean = false

    onClickButton() {

      this.buttonPressed = !this.buttonPressed

    }

  }

</script>

Angular (declarative)


import { Component, OnInit, Input } from '@angular/core'

@Component({

  selector: 'my-app',

  template: `<div class="App">

    <h1> Welcome to this App </h1>

     {item?.name }

    <button (click)="onClickButton($event)"></button>

    <p *ngIf="buttonPressed"> Hello World </p>

  </div>`

})

export class App implements OnInit {

  @Input() item: person;

  buttonPressed: boolean = false;

  onClickButton() {

    this.buttonPressed = !this.buttonPressed;

  }

}

React (declarative)


import React, { Component, Fragment } from 'react'

interface State { buttonPressed: boolean }

class App extends Component<Props, State> {

  state: State = { buttonPressed: false }

  render() {

    return (

      <Fragment>

        <div class="App">

          <h1> Welcome to this App </h1>

          Sample Item Name

           <button onClick={() => this.setState({ buttonPressed: !this.state.buttonPressed })}></button>

          {this.state.buttonPressed && ( <p>Hello World</p>)}

        </div>

      </Fragment>

    )

  }

}

DOM Manipulation

One of the most common operations on web apps is to manipulate the document structure, either by adding or removing or modifying elements on the page. The object representation of the web page is called DOM (Document Object Model) and is a tree-like structure which consists of three different types of nodes: element, text and comment. The DOM can be modified by methods defined in its API. The latter identifies the programming interface on how to navigate through this tree-like structure and how to manipulate the nodes. There is a collection of methods which are used to:

  • add elements
  • change color of elements
  • change attributes of elements
  • add/remove listeners to elements
  • show/hide elements (examples)
  • rendering list of elements (example)
  • conditional css (example)

The modification of the aforementioned tree-object representation via javascript is what we call DOM manipulation and is done in all three frameworks. DOM manipulation is one of the primary usages of Javascript, so it is implemented in both imperative and declarative approaches.

In the declarative ones, however, the DOM manipulation needs to be converted into the imperative way of mutating the DOM, because the latter way is more native and is a direct implementation of the well-specified DOM API methods. In reactive UI frameworks the conversion from declarative expression into imperative way is taking place under the hood and is one of their major roles.

Data-binding

Data binding is an advanced form of dom manipulation because data needs to be retrieved from a service first, then to be manipulated and then to be rendered and shown on the UI. Before explaining into more detail what data-binding is, let’s describe what problem it does solve:

As described in this article the whole experience of ui-development in desktop apps was similar as in web apps with vanilla jQuery:

  1. create inputs
  2. subscribe to its events
  3. write the code that updates inner state based on the state of those inputs/controls

As the application grows the maintenance of the above and the interdependence between inputs would be challenging. This issue is tackled with data-binding, where you tell a specific portion of the UI to be bound to some value from the business layer. Data binding is a connector between user interface and business logic, between destination and source. Namely, it is a general method that connects two different data sources and keeps the data in sync.

All 3 frameworks have some idea of data-binding in which data can be displayed in the UI and updated in memory via the UI.

Data handling

All three frameworks have built in ecosystem solutions for app-wide state management. From a more abstracted layer that means that application data is stored in a shared javascript object, outside the components. There is decoupling between the data layer and a specific view, opposed to the older approaches in which there was service for every view/component. The implementation details of this “globally shared object” differ among the frameworks, and it is outside the scope of this post. In a next article of these series there will be a thorough comparison between the different types of state management.

The new “global-shared-object” is inspired by the flux pattern in contradiction to the older mcv (and mv-vm) architectures. In this new pattern views don’t map directly to domain stores. In addition data flow is unidirectional, which makes it easier to manage the data flow.

Web components

Most modern frontend frameworks support the idea of Web Components and are compatible with them in some of their technologies. Let’s briefly explain what web components are. Web Components are reusable client-side components based on official native web standards and supported by all major browsers. They use a set of specifications such as Custom Elements, Shadow DOM, HTML Templates and ES Modules.

Comparable to frontend frameworks they provide similar functionality but the big difference is that they are native to the browser link. In more technical terms they are low level APIs to build reusable UI components. However, they are not meant to replace the frameworks, as on their own they are not sufficient to handle changes on data and updates on the DOM. In addition there are still quite some limitations regarding custom elements and backwards compatibility with SEO as described here. And as Lea Verou points out, HTML in web components is quite JS heavy and is not treated with the respect it deserves.

It’s often easy to think of web-components as a replacement solution for the UI library. Both web components and modern frameworks have an API for defining components and registering them by name or with a selector. Both web components and frameworks provide lifecycle hooks. Lifecycle hooks are great, because you don’t have to invent a completely new system for constructing and deconstructing elements yourself. In the end, it is always the same with a different syntax: You create components. You need to pass data up or down the DOM tree. You need to render some data.

Except for the compatibility of web components with frontend frameworks from the general perspective of component api, there is compatibility in some more concrete specifications of them as well. For example both Angular and vue use shadow dom, and slots slots. Custom events can be used in all three frameworks as it is the native way of triggering and subscribing events.

Lifecycle hooks

In the previous section we described the similarity between web components and frameworks from the perspective that both provide lifecycle hooks. In this section we will describe in an abstracted way the lifecycle of a component. These generic steps apply in all three frameworks. Such an abstracted analysis has been made also in this article What differs among them are the names of the methods that implement these steps and depending on the framework there could be a more fine-grained approach, with more methods, describing extra, intermediate steps, besides the abstracted ones mentioned above.

These generic stages are visualized in the diagram below in black square boxes. Steps in the first row refer to the phase “Before the creation of a component”, the ones in the second row to the phase “During the lifespan of a component” and the third row refers to the phase “After a component has been destroyed”.

Abstract Lifecycle hooks steps

In prerendering the component does not exist on the screen yet, it has only been referenced in the code. This is an optional step. Any operations that need to happen before the component is mounted in the DOM should happen during this phase. The rendering phase is the most common one and the obligatory one. It is the step where all API calls are made. The Changes/updates phase refers to the phase during which the user interacts with the app and thus the component view changes. Additionally during this phase API updates are retrieved and cause the re rendering of the component. This is also the place where we put code for form validation. Lastly the Destroy phase is when we leave a page. During this phase we should unregister subscriptions listening to data streams, or to reset flags for init state of UI controls (default filter, or expanded accordions etc).

SSR Rendering

Αll three frameworks support server side rendering (SSR). The need for SSR has arisen because client-side rendering of most web applications has been often time-consuming. Users need to wait until scripts are downloaded, parsed and run. With SSR HTML code is rendered on the server, instead of json data to be sent to the client and then the page to be rendered based on them. Sometimes even an improvement of the initial page load to a fraction of a second can have a big business impact. What differs among the frameworks is the tools that are used in each one to implement SSR.

CLI-Webpack

All three frameworks have a CLI that uses Webpack. Webpack is a static module bundler for modern JavaScript applications. It combines all the modules a project needs into one or more bundles, and builds behind the scenes a dependency graph. and runs a front-end development server and does component/module imports and hot reload.

Integration to existing web projects

All three frameworks can be used with existing projects, even if they are not single page applications. In the official documentation of Reat, you can find information on how to add a react into a website. You can choose as much React as you want on the new website, either just a React component or a full app. Similarly Vue can be integrated in existing projects. Here is an example of how this can happen. Lastly, in angular you can achieve something along the lines of this spirit with Angular elements. The latter are the same as custom elements from web components technology.