A brief history of web frameworks
It is essential to understand why we use frameworks such as Angular, React, or Vue in the first place to get an appreciation of the value they bring. As the web evolves, you may find that, in some cases, the framework is no longer necessary and should be discarded, and in others, critical to your business and must be retained. Web frameworks rose as JavaScript became more popular and capable in the browser. In 2004, the Asynchronous JavaScript and XML (AJAX) technique became very popular in creating websites that did not have to rely on full-page refreshes to create dynamic experiences, utilizing standardized web technologies like HTML, JavaScript/ECMAScript, and CSS. Browser vendors are supposed to implement these technologies as defined by the World Wide Web Consortium (W3C).
Internet Explorer (IE) was the browser that most internet users relied on at the time. Microsoft used its market dominance to push proprietary technologies and APIs to secure IE’s edge as the go-to browser. Things started to get interesting when Mozilla’s Firefox challenged IE’s dominance, followed by Google’s Chrome browser. As both browsers successfully gained significant market share, the web development landscape became a mess. New browser versions appeared at breakneck speed. Competing corporate and technical interests led to the diverging implementation of web standards.
This fracturing created an unsustainable environment for developers to deliver consistent experiences on the web. Differing qualities, versions, and names of implementations of various standards created an enormous challenge: successfully writing code that could manipulate the DOM of a browser consistently. Even the slightest difference in the APIs and capabilities of a browser would be enough to break a website.
The jQuery era
In 2006, jQuery was developed to smooth out the differences between APIs and browser capabilities. So instead of repeatedly writing code to check browser versions, you could use jQuery, and you were good to go. It hid away all the complexities of vendor-specific implementations and gracefully filled the gaps when there were missing features. For almost a decade, jQuery became the web development framework. It was unimaginable to write an interactive website without using jQuery.
However, to create vibrant user experiences, jQuery alone was not enough. Native web applications ran all their code in the browser, which required fast computers to run the dynamically interpreted JavaScript and render web pages using complicated object graphs. In the 2000s, many users ran outdated browsers on relatively slow computers, so the user experience wasn’t great.
Combined with AJAX, jQuery enabled any web developer to create interactive and dynamic websites that could run on any browser without running expensive server hardware and software. To have a solid understanding of the architectural nuances of code that runs on the client and server side, consider a traditional three-tier software architecture. Each tier is described in three primary layers, as shown in the following diagram:
Figure 1.2: Three-tiered software architecture
The presentation layer contains User Interface (UI) related code. This is primarily code that runs on the client, referred to as a thick client. However, the presentation logic can instead reside on the server. In these cases, the client becomes a thin client. The business layer contains business logic and normally resides on the server side. An undisciplined implementation can result in business logic spreading across all three layers. This means a bug or a change in the logic needs to be implemented in many locations. In reality, no individual can locate every occurrence of this logic and can only partially repair code. This, of course, results in the creation of more exotic bugs. The persistence layer contains code related to data storage.
To write easy-to-maintain and bug-free code, our overall design goal is to aim for low coupling and high cohesion between the components of our architecture. Low coupling means that pieces of code across these layers shouldn’t depend on each other and should be independently replaceable. High cohesion means that pieces of code related to each other, like code regarding a particular domain of business logic, should remain together. For example, when building an app to manage a restaurant, the code for the reservation system should be together and not spread across other systems like inventory tracking or user management.
With jQuery and AJAX, writing thick clients for the web became possible, making it easier than ever to write unmaintainable code. Modern web apps have way more moving parts than a basic three-tiered application. The diagram that follows shows additional layers that fit around the presentation, business, and persistence layers:
Figure 1.3: Modern Web Architecture
You can observe the essential components of modern web development in the expanded architecture diagram, which includes an API layer that usually transforms and transfers data between the presentation and business layers. Beyond code within the operating environment, the tools and best practices layer defines and enforces patterns used to develop the software. Finally, the testing layer defines a barrage of automated tests to ensure the correctness of code, which is crucial in today’s iterative and fast-moving development cycles.
While there was a big appetite to democratize web development with thick clients primarily consuming client-side computing resources, the tooling wasn’t ready to enforce proper architectural practices and deliver maintainable software. This meant businesses kept investing in server-side rendering technologies.
The server-side MVC era
In the late 2000s, many businesses still relied on server-side rendered web pages. The server dynamically created all the HTML, CSS, and data needed to render a page. The browser acted as a glorified viewer that would display the result. The following is a diagram that shows an example architectural overview of a server-side rendered web application in the ASP.NET MVC stack:
Figure 1.4: Server-side rendered MVC architecture
Model-View-Controller (MVC) is a typical pattern of code that has data manipulation logic in models, business logic in controllers, and presentation logic in views. In the case of ASP.NET MVC, the controller and model are coded using C#, and views are created using a templated version of HTML, JavaScript, and C#. The result is that the browser receives HTML, JavaScript, and needed data, and through jQuery and AJAX magic, web pages look to be interactive. Server-side rendering and MVC patterns are still popular and in use today. There are justified niche uses, such as Facebook.com. Facebook serves billions of devices that range from the very slow to the very fast. Without server-side rendering, it would be impossible for Facebook to guarantee consistent UX across its user base.
The combination of server-side rendering and MVC is an intricate pattern to execute; there are a lot of opportunities for presentation and business logic to become co-mingled. To ensure the low coupling of components, every member of the engineering team must be very experienced. Teams with a high concentration of senior developers are hard to come by, which would be an understatement.
Further complicating matters is that C# (or any other server-side language) cannot run natively in the browser. So developers who work on server-side rendered applications must be equally skilled at using frontend and backend technologies. It is easy for inexperienced developers to unintentionally co-mingle presentation and business logic in such implementations. When this happens, the inevitable UI modernization of an otherwise well-functioning system becomes impossible. In other words, to replace the sink in your kitchen with a new one, you must renovate your entire kitchen. Due to insufficient architecture, organizations spend millions of dollars writing and rewriting the same applications every decade.
Rich client era
In the 2000s, it was possible to build rich web applications decoupled from their server APIs using Java Applets, Flash, or Silverlight. However, these technologies relied on browser plugins that needed a separate installation. Most often, these plugins were outdated, created critical security vulnerabilities, and consumed too much power on mobile computers. Following the iPhone revolution in 2008, it was clear such plugins wouldn’t run on mobile phones, despite the best attempts by the Android OS. Besides, Apple CEO Steve Jobs’ disdain for such inelegant solutions marked the beginning of the end for the support of such technologies in the browser.
In the early 2010s, frameworks like Backbone and AngularJS started showing up, demonstrating how to build rich web applications with a native feel and speed and do so in a seemingly cost-effective way. The following diagram shows a Model-View-ViewModel (MVVM) client with a Representational State Transfer (REST) API. When we decouple the client from the server via an API, we can architecturally enforce the implementation of presentation and business logic separately. Theoretically, this RESTful web services pattern would allow us to replace the kitchen sink as often as possible without remodeling the entire kitchen.
Figure 1.5: Rich-client decoupled MVVM architecture
The MVVM architecture above shows a near doubling of boxes compared to the server-side MVC architecture. Does this mean we need to write twice as much code? Yes and no. Yes, we need to write more code to maintain a disciplined architecture; however, over time, we’ll write a lot less code because of the overall maintainability of the solution. The architecture surrounding the presentation logic indeed becomes a lot more complicated. The client and server must implement their presentation/API, business, and persistence layers.
Unfortunately, many early development efforts leveraging frameworks like Backbone and AngularJS collapsed under their weight because they failed to implement the client-side architecture properly.
These early development efforts also suffered from ill-designed RESTful Web APIs. Most APIs didn’t version their URIs, making it very difficult to introduce new functionality while supporting existing clients. Further, APIs often returned complicated data models exposing their internal relational data models to web apps. This design flaw creates a tight coupling between seemingly unrelated components/views written in HTML and models created in SQL. If you don’t implement additional layers of code to translate or map the structure of data, then you create an unintentional and uncontrolled coupling between layers. Over time, dealing with such coupling becomes very expensive very quickly, in most cases necessitating significant rewrites.
Today, we use the API layer to flatten the data model before sending it to the client to avoid such problems. Newer technologies like GraphQL go further by exposing a well-defined data model and letting the consumer query for the exact data it needs. Using GraphQL, the number of HTTP requests and the amount of data transferred over the wire is optimal without the developers having to create many specialized APIs.
Backbone and AngularJS proved that creating web applications that run natively in the browser was viable. All SPA frameworks at the time relied on jQuery for DOM manipulation. Meanwhile, web standards continued to evolve, and evergreen browsers supporting new standards became commonplace. However, change is constant, and the evolution of web technologies made it unsustainable to gracefully evolve this first generation of SPA frameworks, as I hinted in the Two Angulars section.
The next generation of web frameworks needed to solve many problems; they needed to enforce good architecture, be designed to evolve with web standards and be stable and scalable to enterprise needs without collapsing. Also, these new frameworks needed to gain acceptance from developers, who were burned out with too many rapid changes in the ecosystem. Remember, unhappy developers do not create successful businesses. Achieving these goals required a clean break from the past, so Angular and React emerged as platforms to address the problems of the past in different ways. As you’ll discover in the following sections, Angular offers the best tools and architecture for building scalable enterprise-grade applications.