At first, there was HTML, then DHTML. Technologists invented new technologies like Java, JavaScript, PHP, and many others to deliver interactive experiences over the browser. The holy grail of programming was writing a program once and running it everywhere. In a flash, the era of Single-Page Applications (SPAs) was born. SPAs tricked the browser into thinking that a single index.html
could house entire applications containing many pages. Backbone.js, Knockout.js, and Angular.js all came and went. Everyone reeling from unmanaged complexity and JavaScript-framework-of-the-week syndrome looked for a savior. Then came React, Angular, and Vue. They promised to fix all problems, bring about universally reusable web components, and make it easier to learn, develop, and scale web applications. And, so they did! Some better than others. The adolescent history of the web has taught us a couple of essential lessons. First, change is inevitable, and second, the developer's happiness is a precious commodity that can make or break entire companies.
This chapter covers:
This first chapter is meant to give you a theoretical and historical background for the rest of the book. Feel free to use it as a reference as you go through the rest of the book. Chapter 2, Setting Up Your Development Environment, covers how you can configure your development environment for a great development experience. With Chapter 3, Creating a Basic Angular App, you begin implementing your first Angular application. If you're already experienced with Angular, you may start with Chapter 7, Creating a Router-First Line-of-Business App, to dive into creating scalable applications ready for the enterprise.
Each chapter in the book introduces you to new concepts and reinforces best practices while covering optimal ways of working with widely used and open source tools. Along the way, tips and information boxes cover the bases to close any knowledge gaps you may have about web and modern JavaScript development basics. As you go through the content, pay attention to numbered steps or bullet points as they describe actions you need to take. If you skip a section or a chapter, you may miss subtle changes in configuration or techniques that may confuse you later on.
The code samples provided in this book have been developed using Angular 9, which is planned to be in Long-Term Support (LTS) until August 2021. The chances are that you are reading this book after new versions have superseded Angular 9. However, worry not. This book adopts the Angular evergreen motto of always keeping the version of Angular up to date with the latest release. Keeping up to date is made possible by sticking to platform fundamentals and avoiding unnecessary third-party libraries. The example projects for the book were initially written for Angular 5 and updated over time without major rewrites by following a proactive and incremental Angular upgrade schedule. I anticipate these projects to survive with minor modifications for years to come. This reliability is a testament to the excellent compatibility work done by the Angular team.
The world of JavaScript, TypeScript, and Angular is constantly changing. It is normal for there to be some differences between code samples in the book and the code that is generated for you by the tools you use. For this reason, most of the best practices and configuration items recommended by this book are applied using tools that I created, so they can be updated. Below is a high-level overview of the collection of libraries, extensions, and open source projects that support the content of the book:
Figure 1.1: Code developed in support of this book
The preceding diagram is to give you a quick glance at some of the moving parts. Each component is detailed in the coming chapters. The most up-to-date versions of the sample code for the book are on GitHub, at the repositories linked below. These repositories contain the final and completed state of the code. To make it easier to verify your progress at the end of a chapter, the projects
folder in each repository contains chapter-by-chapter snapshots reflecting the current state of the code:
You may read more about updating Angular in Appendix C, Keeping Angular and Tools Evergreen. You can find this appendix online from https://static.packt-cdn.com/downloads/9781838648800_Appendix_C_Keeping_Angular_and_Tools_Evergreen.pdf or at https://expertlysimple.io/stay-evergreen.
Let's take a look at the last 20 or so years of web development history, so you can contextualize how Angular came to be and evolved.
It is essential to consider why we use frameworks such as Angular, React, or Vue in the first place. Web frameworks came to rise 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 the vast majority of 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, which was successfully writing code that could manipulate the Document Object Model (DOM) of a browser consistently. Even the slightest difference in the APIs and capabilities of a browser would be enough to break a website.
In 2006, jQuery was developed to smooth out the differences between APIs and capabilities for browsers. 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 a good 5 to 6 years, jQuery became the web development framework. It was unimaginable to write an interactive website without using jQuery.
To create vibrant user experiences, however, 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 the complicated object graphs. Back in the 2000s, many users ran outdated browsers on relatively slow computers, so the user experience wasn't great.
Traditionally, software architecture is described in three primary layers, as shown in the diagram that follows:
Figure 1.2: Three-tiered software architecture
The presentation layer contains user interface (UI) related code, the business layer contains business logic, and the persistence layer contains code related to data storage. It is an overall design goal 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 that are 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. Modern web development has 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
In the preceding diagram, you can see an expanded architecture diagram that includes essential components of modern web development, which include an API layer that usually transforms data between the presentation and business layers, a tools and best practices layer that defines various methodologies used to develop the software, and an automated testing layer that is crucial in today's iterative and fast-moving development cycles.
In the 2000s, many internet companies 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 a sample 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 data that is needed, 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 a consistent user experience (UX) across its userbase. I find the combination of server-side rendering and MVC to be an intricate pattern to execute. 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, and that 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 co-mingle presentation and business logic in such implementations unintentionally. When this happens, the inevitable UI modernization of an otherwise well-functioning system becomes impossible. Put in other terms, to replace the sink in your kitchen with a new one, you must renovate your entire kitchen. Due to insufficient architecture, organizations routinely spend millions of dollars every 10 years writing and rewriting the same applications.
During the 2000s, it was possible to build rich web applications that were 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 out of date, 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 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 to them and do so in a seemingly cost-effective way. The diagram that follows 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, then we can architecturally enforce the implementation of presentation and business logic separately. In theory, this RESTful web services pattern should allow us to replace the kitchen sink as often as we want to without having to remodel the entire kitchen.
Figure 1.5: Rich-client decoupled MVVM architecture
Observe the near doubling of boxes in the preceding diagram. Just because we separate the client from the server, we don't end up simplifying the architecture. If anything, the architecture surrounding the presentation logic becomes a lot more complicated. Both 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 own 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 down to the client to avoid such problems. Newer technologies like GraphQL go a step 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 it was viable to create web applications that run natively in the browser. All SPA frameworks at the time relied on jQuery for DOM manipulation. Meanwhile, web standards continued to evolve, and evergreen browsers that support new standards started to become commonplace. However, change is constant, and the evolution of web technologies made it unsustainable to evolve this first generation of SPA frameworks gracefully.
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.
Angular is an open source project maintained by Google and a community of developers. The new Angular platform is vastly different from the legacy framework you may have used in the past. In collaboration with Microsoft, Google made TypeScript the default language for Angular. TypeScript is a superset of JavaScript that enables developers to target legacy browsers such as Internet Explorer 11, while allowing them to write modern JavaScript code that works in evergreen browsers such as Chrome, Firefox, and Edge. The legacy versions of Angular, versions in the 1.x.x range, are referred to as AngularJS. Version 2.0.0 and higher versions are called Angular. Where AngularJS is a monolithic JavaScript SPA framework, Angular is a platform that is capable of targeting browsers, hybrid-mobile frameworks, desktop applications, and server-side rendered views.
Upgrading to the new AngularJS was risky and costly because even minor updates introduced new coding patterns and experimental features. Each update introduced deprecations or the refactoring of old features, which required rewriting large portions of code. Also, updates were delivered in uncertain intervals, making it impossible for a team to plan resources to upgrade to a new version. The release methodology eventually led to an unpredictable, ever-evolving framework with seemingly no guiding hand to carry code bases forward. If you used AngularJS, you likely were stuck on a particular version, because the specific architecture of your code base made it very difficult to move to a new version. In 2018, the Angular team released the last major update to AngularJS with version 1.7. This release marked the beginning of the end for the legacy framework, with planned end-of-life in July 2021.
Angular improves upon AngularJS in every way imaginable. The platform follows semver, as defined at https://semver.org/, where minor version increments denote new feature additions and potential deprecation notices for the second next major version, but no breaking changes. Furthermore, the Angular team at Google has committed to a deterministic release schedule with major versions released every 6 months. After this 6-month development window, starting with Angular 4, all major releases receive LTS with bug fixes and security patches for an additional 12 months. From release to end-of-life, each major version receives updates for 18 months. Refer to the following chart for the tentative release and support schedule for AngularJS and Angular:
Figure 1.6: Tentative Angular release and support schedule
So, what does this mean for you? You can be confident that your Angular code is supported and backward compatible for an approximate time frame of 24 months, even if you make no changes to it. So, if you wrote an Angular app in version 9 in February 2020, your code is runtime compatible with Angular 10 and will be supported until October 2021. To upgrade your Angular 9 code to Angular 11, you need to ensure that you're not using any of the deprecated APIs that receive a deprecation notice in Angular 10.
In practice, most deprecations are minor and are straightforward to refactor. Unless you are working with low-level APIs for highly specialized user experiences, the time and effort it takes to update your code base should be minimal. However, this is a promise made by Google and not a contract. The Angular team has a significant incentive to ensure backward compatibility because Google runs around 1,000+ Angular apps with a single version of Angular active at any one time throughout the organization. So, by the time you read this, all of Google's 1,000+ apps will be running on the latest version of Angular.
You may think Google has infinite resources to update thousands of app regularly. Like any organization, Google too has limited resources, and not every app is actively maintained by a dedicated team. So, the Angular team must ensure compatibility through automated tests and make it as painless as possible to move through major releases going forward. In Angular 6, the update process was made much simpler with the introduction of ng update
.
The Angular team continually improves its release process with automated CLI tools to make upgrades of deprecated functionality a mostly automated, reasonable endeavor. The benefits of this strategy were demonstrated by Air France and KLM being able to reduce their upgrade times from 30 days in Angular 2 to 1 day in Angular 7.
A predictable and well-supported upgrade process is excellent news for developers and organizations alike. Instead of being perpetually stuck on a legacy version of Angular, you can plan and allocate the necessary resources to keep moving your application to the future without costly rewrites. As I wrote in a 2017 blog post, The Best New Feature of Angular 4, at bit.ly/NgBestFeature
, the message is clear:
For Developers and Managers: Angular is here to stay, so you should be investing your time, attention, and money in learning it – even if you're currently in love with some other framework.
For Decision Makers (CIOs, CTOs, and so on): Plan to begin your transition to Angular in the next 6 months. It'll be an investment you'll be able to explain to business-minded people, and your investment will pay dividends for many years to come, long after the initial LTS window expires, with graceful upgrade paths to Angular vNext and beyond.
So, why do Google (Angular) and Microsoft (TypeScript and Visual Studio Code) give away such technologies for free? There are multiple reasons:
I don't see any nefarious intent here and welcome open, mature, and high-quality tools that, if necessary, I can tinker with and bend to my own will. Not having to pay for a support contract for a proprietary piece of tech is a welcome bonus.
Beware, looking for Angular help on the web may be tricky. You'll note that sometimes Angular is referred to as Angular 2 or Angular 4. At times, both Angular and AngularJS are referred to as AngularJS. This is incorrect. The documentation for Angular is at angular.io. If you land on angularjs.org, you'll be reading about the legacy AngularJS framework.
For the latest updates on the upcoming Angular releases, view the official release schedule at https://angular.io/guide/releases.
Your time is valuable, and your happiness is paramount, so you must be careful in choosing the technologies to invest your time in. With this in mind, we need to answer the question of why learn Angular, but not React, Vue, or some other framework? Angular is a great framework to start learning. The framework and the tooling help you get off the ground quickly and continue being successful with a vibrant community and high-quality UI libraries you can use to deliver exceptional web applications. React and Vue are great frameworks, with their strengths and weaknesses. Every tool has its place and purpose.
In some cases, React is the right choice for a project, and in other cases, Vue is the right one. Regardless, becoming somewhat proficient in other web frameworks can only help further your understanding of Angular and make you a better developer overall. SPAs such as Backbone and AngularJS grabbed my full attention in 2012 when I realized the importance of decoupling frontend and backend concerns. Server-side rendered templates are nearly impossible to maintain and are the root cause of many expensive rewrites of software systems. If you care about creating maintainable software, then you must abide by the prime directive; keep business logic implemented behind the API decoupled from presentation logic implemented in the UI.
Angular neatly fits the Pareto principle or the 80-20 rule. It has become a mature and evolving platform, allowing you to achieve 80% of tasks with 20% of the effort. As mentioned in the previous section, every major release is supported for 18 months, creating a continuum of learning, staying up to date, and the deprecation of old features. From the perspective of a full-stack developer, this continuum is invaluable, since your skills and training will remain relevant and fresh for many years to come.
The philosophy behind Angular is to err on the side of configuration over convention. Convention-based frameworks, although they may seem elegant from the outside, make it difficult for newcomers to pick up the framework. Configuration-based frameworks, however, aim to expose their inner workings through explicit configuration and hooks, where you can attach your custom behavior to the framework. In essence, where AngularJS had tons of magic, which can be confusing, unpredictable, and challenging to debug, Angular tries to be non-magical.
Configuration over convention results in verbose coding. Verbosity is a good thing. Terse code is the enemy of maintainability, only benefiting the original author. As Andy Hunt and David Thomas put it in The Pragmatic Programmer:
Remember that you (and others after you) will be reading the code many hundreds of times, but only writing it a few times.
Further, Andy Hunt's Law of Design dictates:
If you can't rip every piece out easily, then the design sucks.
Verbose, decoupled, cohesive, and encapsulated code is the key to future-proofing your code. Angular, through its various mechanisms, enables the proper execution of these concepts. It gets rid of many custom conventions invented in AngularJS, such as ng-click
, and introduces a more natural language that builds on the existing HTML elements and properties. As a result, ng-click
becomes (click)
, extending HTML rather than replacing it.
Next, we'll go over Angular's evergreen mindset and the reactive programming paradigm, which are the latest extensions of Angular's initial philosophy.
When you're learning Angular, you're not learning one specific version of Angular, but a platform that is continually evolving. Since the first drafts, I designed this book with the idea of deemphasizing the specific version of Angular you're using. The Angular team champions this idea. Over the years, I have had many conversations with the Angular team and thought leaders within the community and listened to many presentations. As a result, I can affirm that you can depend on Angular as a mature web development platform. Angular frequently receives updates with great attention to backward compatibility. Furthermore, any code that is made incompatible by a new version is brought forward with help from automated tools or explicit guidance on how to update your code via update.angular.io, so you're never left guessing or scouring the internet for answers. The Angular team is committed to ensuring you – the developer – have the best web development experience possible.
To bring this idea front and center with developers, several colleagues and I have developed and published a Visual Studio Code extension called Angular Evergreen.
Figure 1.7: Angular Evergreen VS Code extension
This extension detects your current version of Angular and compares it to the latest and next releases of Angular. Releases that are labeled next are meant for early adopters and for testing the compatibility of your code with an upcoming version of Angular. Do not use next-labeled releases for production deployments.
Find more information, feature requests, and bug reports on the Angular Evergreen extension at https://AngularEvergreen.com.
One of the critical components of Angular that allows the platform to remain evergreen is TypeScript. TypeScript allows new features to be implemented efficiently while providing support for older browsers, so your code can reach the widest audience possible.
Angular is coded using TypeScript. TypeScript was created by Anders Hejlsberg of Microsoft to address several major issues with applying JavaScript at the enterprise-scale.
Anders Hejlsberg is the creator of Turbo Pascal and C#, and is the chief architect of Delphi. Anders designed C# to be a developer-friendly language built upon the familiar syntax of C and C++. As a result, C# became the language behind Microsoft's popular .NET Framework. TypeScript shares a similar pedigree with Turbo Pascal and C# and their ideals, which made them a great success.
JavaScript is a dynamically interpreted language, where the code you write is parsed and understood by the browser at runtime. Statically typed languages like Java or C# have an additional compilation step, where the compiler can catch programming and logic errors during compile time. It is much cheaper to detect and fix bugs at compile time versus runtime. TypeScript brings the benefits of statically typed languages to JavaScript by introducing types and generics to the language. However, TypeScript does not include a compilation step, but instead a transpilation step. A compiler builds code into machine language with C/C++ or intermediary language (IL) with Java or C#. A transpiler, however, merely translates code from one dialect to another. So, when TypeScript code is built, compiled, or transpiled, the result is pure JavaScript.
JavaScript's official name is ECMAScript. The feature set and the syntax of the language is maintained by the ECMA Technical Committee 39 or TC39 for short.
Transpilation has another significant benefit. The same tooling that converts TypeScript to JavaScript can be used to rewrite JavaScript with a new syntax to an older version that older browsers can parse and execute. Between 1999 and 2009, the JavaScript language didn't see any new features. ECMAScript abandoned version 4 due to various technical and political reasons. Starting with the introduction of ES5 and then ES2015 (also known as ES6), browser vendors have struggled to implement new JavaScript features within their browsers. As a result, user adoption of these new features has remained low. However, these new features meant developers could write code more productively. This created a gap known as the JavaScript Feature Gap, as demonstrated by the graphic that follows:
Figure 1.8: The JavaScript Feature Gap
The JavaScript Feature Gap is a sliding one, as TC39 has committed to updating JavaScript every year going forward. As a result, TypeScript represents the past, present, and future of JavaScript. You can use future features of JavaScript today and still be able to target browsers of the past to maximize the audience you can reach.
Now, let's go over Angular's underlying architecture.
Angular follows the MV* pattern, which is a hybrid of the MVC and MVVM patterns. Previously, we went over the MVC pattern. At a high-level, the architecture of both patterns is relatively similar, as shown in the diagram that follows:
Figure 1.9: MV* architecture
The new concept here is the ViewModel, which represents the glue code that connects your view to your model or service. In Angular, this glue is known as binding. Whereas MVC frameworks like Backbone or React have to call a render
method to process their HTML templates, in Angular, this process is seamless and transparent for the developer. Binding is what differentiates an MVC application from an MVVM one.
The most basic unit of an Angular app is a component. A component is the combination of a JavaScript class written in TypeScript and an Angular template written in HTML, CSS, and TypeScript. The class and the template fit together like a jigsaw puzzle through bindings, so that they can communicate with each other, as shown in the diagram that follows:
Figure 1.10: Anatomy of a component
Classes are an Object-Oriented Programming (OOP) construct. If you invest the time to dig deeper into the OOP paradigm, you are going to improve your understanding of how Angular works vastly. The OOP paradigm allows for the dependency injection (DI) of dependent services in your components, so you can make HTTP calls or trigger a toast message to be displayed to the user without pulling that logic into your component or duplicating your code. DI makes it very easy for developers to use many interdependent services without having to worry about the order of instantiation, initialization, or destruction of such objects from memory.
Angular templates also allow similar reuse of code via directives, pipes, user controls, and other components. These are pieces of code that encapsulate highly interactive end user code. This kind of interactivity code is often complicated and convoluted and must be kept isolated from business logic or presentation logic to keep your code maintainable.
All Angular components, services, directives, pipes, and user controls are organized under modules. Each Angular app is bootstrapped by a root module that renders your first component and injects any services and prepares dependencies it may require. You may introduce children modules to enable capabilities like lazy loading so that you don't have to deliver all components of your web application to the browser all at once. For instance, there is no use sending code for the admin dashboard to a user without admin privileges.
Angular makes heavy use of the RxJS library, which introduces reactive development patterns to Angular, as opposed to more traditional imperative development patterns.
Angular supports multiple styles of programming. The plurality of coding styles is one of the great reasons why it is approachable to programmers with varying backgrounds. Whether you come from an object-oriented programming background or you're a staunch believer of functional programming, you can build viable apps using Angular. In Chapter 3, Creating a Basic Angular App, you'll begin leveraging reactive programming concepts in building the LocalCast Weather app.
As a programmer, you are most likely used to imperative programming. Imperative programming is when you, as the programmer, write sequential code describing everything that must be done in the order that you've defined them and the state of your application depending on just the right variables to be set to function correctly. You write loops, conditionals, and call functions; you fire off events and expect them to be handled. Imperative and sequential logic is how you're used to coding.
Reactive programming is a subset of functional programming. In functional programming, you can't rely on variables you've set previously. Every function you write must stand on its own, receive its own set of inputs and return a result without being influenced by the state of an outer function or class. Functional programming supports Test Driven Development (TDD) very well because every function is a unit that can be tested in isolation. As such, every function you write becomes composable. So, you can mix, match, and combine any function you write with any other and construct a series of calls that yield the result you expect.
Reactive programming adds a twist to functional programming. You no longer deal with pure logic, but an asynchronous data stream that you transform and mold into any shape you need with a composable set of functions. So, when you subscribe to an event in a reactive stream, then you're shifting your coding paradigm from reactive programming to imperative programming.
Later in the book, when implementing the LocalCast Weather app, you'll leverage subscribe
in action in two places, in the CurrentWeather
and CitySearch
components.
Consider the following example, aptly put by Mike Pearson in his presentation
Thinking Reactively: Most Difficult, of providing instructions to get hot water from the faucet to help understand the differences between imperative and reactive programming:
Instructions to get hot water from the faucet | ||
Imperative | Reactive | |
0 |
Initial state: Water is off |
Initial state: Water is off |
1 |
Grab a hose |
Turn on the faucet for hot water |
2 |
Spray water into the heater |
|
3 |
Turn on the faucet for hot water |
|
4 |
Send a text to the utility company to get gas |
|
5 |
Wait for hot water |
|
6 |
Undo your steps to restore the initial state |
Undo your steps to restore the initial state |
As you can see, with imperative programming, you must define every step of the code execution. Every step depends on the previous step, which means you must consider the state of the environment to ensure a successful operation. In such an environment, it is easy to forget a step and very difficult to test the correctness of every individual step. In functional reactive programming, you work with asynchronous data streams resulting in a stateless workflow that is easy to compose with other actions.
RxJS is the library that makes it possible to implement your code in the reactive paradigm.
RxJS stands for Reactive Extensions, which is a modular library that enables reactive programming, which itself is an asynchronous programming paradigm and allows the manipulation of data streams through transformation, filtering, and control functions. You can think of reactive programming as an evolution of event-based programming.
In event-driven programming, you would define an event handler and attach it to an event source. In more concrete terms, if you had a Save button, which exposes an onClick
event, you would implement a confirmSave
function which, when triggered, would show a popup to ask the user Are you sure?. Look at the following diagram for a visualization of this process.
Figure 1.11: Event-driven implementation
In short, you would have an event firing once per user action. If the user clicks on the Save button many times, this pattern will gladly render as many popups as there are clicks, which doesn't make much sense.
The publish-subscribe (pub/sub) pattern is a different type of event-driven programming. In this case, we can write multiple handlers to all act on the result of a given event simultaneously. Let's say that your app just received some updated data. The publisher goes through its list of subscribers and passes on the updated data to each of them.
Refer to the following diagram on how the updated data event triggers multiple functions:
updateCache
function updates your local cache with new datafetchDetails
function retrieves further details about the data from the servershowToastMessage
function informs the user that the app just received new dataFigure 1.12: Pub/sub pattern implementation
All these events can happen asynchronously; however, the fetchDetails
and showToastMessage
functions will be receiving more data than they need, and it can get convoluted to try to compose these events in different ways to modify application behavior.
In reactive programming, everything is treated as a stream. A stream will contain events that happen over time and these events can contain some data or no data. The following diagram visualizes a scenario where your app is listening for mouse clicks from the user. Uncontrolled streams of user clicks are meaningless. You exert some control over this stream by applying the throttle
function to it, so you only get updates every 250 milliseconds (ms). If you subscribe to this new event, every 250 ms, you will receive a list of click events. You may try to extract some data from each click event, but in this case, you're only interested in the number of click events that happened. We can shape the raw event data into a number of clicks using the map
function.
Further down the stream, we may only be interested in listening for events with two or more clicks in it, so we can use the filter
function to only act on what is essentially a double-click event. Every time our filter event fires, it means that the user intended to double-click, and you can act on that information by popping up an alert.
The true power of streams comes from the fact that you can choose to act on the event at any time as it passes through various control, transformation, and filter functions. You can choose to display click data on an HTML list using *ngFor
and Angular's async
pipe, so the user can monitor the types of click data being captured every 250 ms.
Figure 1.13: A reactive data stream implementation
Now let's consider some more advanced Angular architectural patterns.
As mentioned earlier, in the Basic Angular architecture section, Angular components, services, and dependencies are organized into modules. Angular apps are bootstrapped via their root module, as shown in the diagram that follows:
Figure 1.14: Angular Bootstrap process showing major architectural elements
The root module can import other modules and also declare components and provide services. As your application grows, you need to create sub-modules that contain their components and services. Organizing your application in this manner allows you to implement lazy loading, allowing you to control which parts of your application get delivered to the browser and when. As you add more features to your application, you import modules from other libraries, like Angular Material or NgRx. You implement the router to enable rich navigational experiences between your components, allowing your routing configuration to orchestrate the creation of components.
Chapter 7, Creating a Router-First Line-of-Business App, introduces router-first architecture, where I encourage you to start the development of your application by creating all your routes ahead of time.
In Angular, services are provided as singletons to a module by default. You'll quickly get used to this behavior. However, you must keep in mind that if you provide the same service across multiple modules, then each module has its own instance of the provided service. In the case of an authentication service, where we wish to have only one instance across our entire application, you must be careful to only provide that instance of the authentication service at the root module level. Any service, component, or module provided at the root level of your application becomes available in the feature module.
Beyond modules, the router is the next most powerful technology you must master in Angular.
The Angular Router, shipped in the @angular/router
package, is a central and critical part of building single-page applications (SPAs) that act and behave like regular websites that are easy to navigate using browser controls or the zoom or micro zoom controls.
The Angular Router has advanced features such as lazy loading, router outlets, auxiliary routes, smart active link tracking, and the ability to be expressed as an href
, which enables a highly flexible Router-first app architecture leveraging stateless data-driven components using RxJS BehaviorSubject
.
Large teams can work against a single code base, with each team responsible for a module's development, without stepping on each other's toes, while enabling easy continuous integration. Google, with its billions of lines of code, works against a single code base for a very good reason: integration after the fact is very expensive.
Small teams can remix their UI layouts on the fly to quickly respond to changes without having to rearchitect their code. It is easy to underestimate the amount of time wasted due to late game changes in layout or navigation. Such changes are easier to absorb by larger teams but a costly endeavor for small teams.
Consider the diagram that follows, where app.ts
contains the module. It has a rootRouter
; components a
, master
, detail
, and c
; services
; pipes
; and directives
provided and declared for it. All of these components will be parsed and eagerly loaded by the browser when a user first navigates to your application.
Figure 1.15: Angular architecture
If you were to implement a lazily loaded route /b
, you would need to create a feature module named b
, which would have its own childRouter
; components d
, e
, and f
; services
; pipes
; and directives
provided and declared for it. During transpile-time, Angular will package these components into a separate file or bundle and this bundle will only be downloaded, parsed, and loaded if the user ever navigates to a path under /b
.
Let's look into lazy loading in more detail.
The dashed line connecting /b/...
to rootRouter
demonstrates how lazy loading works. Lazy loading allows developers to achieve a sub-second first meaningful paint quickly. By deferring the loading of additional modules, we can keep the bundle size delivered to the browser to a minimum. The size of a module impacts download and loading speeds, because the more a browser has to do, the longer it takes for a user to see the first screen of the app. By defining lazily loaded modules, each module is packaged as separate files, which can be downloaded and loaded individually and on demand.
The Angular Router provides smart active link tracking, which results in a superior developer and user experience, making it very easy to implement highlighting features to indicate to the user the current tab or portion of the app that is currently active. Auxiliary routes maximize the reuse of components and help pull off complicated state transitions with ease. With auxiliary routes, you can render multiple master and detail views using only a single outer template. You can also control how the route is displayed to the user in the browser's URL bar and compose routes using routerLink
, in templates, and Router.navigate
, in code, driving complicated scenarios.
In Chapter 7, Creating a Router-First Line-of-Business App, I cover implementing router basics, and advanced recipes are covered in Chapter 11, Recipes – Reusability, Routing, and Caching.
Beyond routing, state management is another crucial concept to master if you would like to build sophisticated Angular applications.
A class backs every component and service in Angular. When instantiated, a class becomes an object in memory. As you work with an object, if you store values in object properties, then you're introducing state to your Angular application. If unmanaged, the state becomes a significant liability to the success and maintainability of your application.
I'm a fan of stateless design both in the backend and frontend. From my perspective, state is evil, and you should pay careful attention not to introduce state into your code. Earlier, we discussed how services in Angular are singletons by default. This is a terrible opportunity to introduce state into your application. You must avoid storing information in your services. In Chapter 7, Creating a Router-First Line-of-Business App, I introduce you to BehaviorSubjects, which act as data-anchors for your application. In this case, we store these anchors in services, so they can be shared across components to synchronize data.
In Angular components, the class is a ViewModel acting as the glue code between your code and the template. Compared to services, components are relatively short-lived, and it is okay to use object properties in this context.
However, beyond design, there are specific use cases for introducing robust mechanisms to maintain complicated data models in the state of your application. Progressive web applications and mobile applications are one use case where connectivity is not guaranteed. In these cases, being able to save and resume the entire state of your application is a must to provide a great user experience (UX) for your end user.
The NgRx library for Angular leverages the Flux pattern to enable sophisticated state management for your applications. In Chapter 6, Forms, Observables, and Subjects and Chapter 12, Recipes – Master/Detail, Data Tables, and NgRx, I provide alternative implementations for various features using NgRx to demonstrate the differences in implementation between more lightweight methods.
Flux is the application architecture that was created by Facebook to assist in building client-side web applications. The Flux pattern defines a series of components that manage a store that stores the state of your application via dispatchers that trigger/handle actions and view functions that read values from the store. Using the Flux pattern, you keep the state of your application in a store where access to the store is only possible through well-defined and decoupled functions, resulting in architecture that scales well because, in isolation, decoupled functions are easy to reason with and write automated unit tests for.
Consider the diagram that follows to understand the flow of information between these components:
Figure 1.16: NgRx data flow
NgRx implements the Flux pattern in Angular using RxJS.
The NgRx library brings Redux-like (a popular React.js library) reactive state management to Angular based on RxJS. State management with NgRx allows developers to write atomic, self-contained, and composable pieces of code creating actions, reducers, and selectors. This kind of reactive programming allows side-effects in state changes to be isolated and feels right at home with the general coding patterns of React.js. NgRx ends up creating an abstraction layer over already complex and sophisticated tooling like RxJS.
There are excellent reasons to use NgRx, like if you deal with 3+ input streams into your application. In such a scenario, the overhead of dealing with so many events makes it worthwhile to introduce a new coding paradigm to your project. However, most applications only have two input streams: REST APIs and user input. To a lesser extent, NgRx may make sense if you are writing offline-first Progressive Web Apps (PWAs), where you may have to persist complicated state information, or architecting a niche enterprise app with similar needs.
Here's an architectural overview of NgRx:
Consider the very top of the diagram as an observable action stream, where actions can be dispatched and acted upon as denoted by the circles. Effects and components can dispatch an action. Reducers and effects can act upon these actions to either store values in the store or trigger an interaction with the server. Selectors are leveraged by components to read values from the store.
Given my positive attitude toward minimal tooling and a lack of definite necessity for NgRx beyond the niche audiences previously mentioned, I do not recommend NgRx as a default choice. RxJS/BehaviorSubjects are powerful and capable enough to unlock sophisticated and scalable patterns to help you build great Angular applications, as is demonstrated in the chapters that lead up to Chapter 12, Recipes – Master/Detail, Data Tables, and NgRx.
You can read more about NgRx at https://ngrx.io.
In contrast to Angular, React.js, as a whole, implements the Flux pattern. Following is a router-centric view of a React application, where components/containers and providers are represented in a strict tree-like manner.
Figure 1.18: React.js architectural overview
In the initial releases of React, one had to laboriously pass values up/down the inheritance tree of every component for even the most basic functionality to work. Later on, react-redux was introduced, so each component can read/write values directly to the store without having to traverse the tree.
This basic overview should give you a sense of the significant architectural differences between Angular and React. However, keep in mind that just like Angular, React, its community, patterns, and practices are continually evolving and getting better over time.
You can learn more about React at https://reactjs.org.
Specific Angular versions introduce noteworthy changes to advance the philosophy of the platform and make it more seamless and comprehensive. I recommend checking out the unique changes that these seminal releases have introduced.
Most, if not all, of the content, patterns, and practices in this book are compatible with Angular 4 and up. However, Angular 6 was a seminal release of Angular, which brought a lot of under-the-covers improvements to the platform and the overall stability and cohesion across the ecosystem. The development experience is vastly improved with additional CLI tools that make it easier to update versions of packages and faster build times to improve your code-build-view feedback cycle. With Angular 6, all platform tools are version synced to 6.0, making it easier to reason about the ecosystem. In the following table, you can see how this makes it easier to communicate tooling compatibility:
Previously | With v6 | |
CLI |
1.7 |
6.0 |
Angular |
5.2.10 |
6.0 |
Material |
5.2.4 |
6.0 |
Angular CLI 6.0 comes with major new capabilities, such as ng update
and ng add
commands; ng update
makes it much easier to update your version of Angular, npm dependencies, RxJS, and Angular Material, including some deterministic code rewriting capabilities to apply name changes to APIs or functions. The topic of updating your version of Angular is covered in depth in Appendix C, Keeping Angular and Tools Evergreen. You can find this appendix online from https://static.packt-cdn.com/downloads/9781838648800_Appendix_C_Keeping_Angular_and_Tools_Evergreen.pdf or at https://expertlysimple.io/stay-evergreen. ng add
brings schematics support to the Angular CLI. With schematics, you can write custom code to add new capabilities to an Angular app, adding any dependencies, boilerplate configuration code, or scaffolding. A great example is to be able to add Angular Material to your project by executing ng add @angular/material
. The topic of adding Angular Material to your project is covered in depth in Chapter 5, Delivering High-Quality UX with Material. A standalone Material Update tool aims to make Angular Material updates less painful, found at Github.com/angular/material-update-tool, but expect this functionality to be merged into ng update
. Further schematics can bring their own generate
commands to CLI, making your life easier and your code base more consistent over time. In addition, version 4 of webpack is configured to build your Angular application into smaller modules with scope hosting, shortening the first-paint time of your app.
The major theme of Angular 6 is under-the-hood performance improvements and Custom Elements support. Version 6 improves upon v5 in terms of the base bundle size by 12% at 65 KB, which improves load times by a whopping 21-40% from fast 3G to fiber connections. As your applications grow, Angular takes advantage of a better tree-shaking technique to further prune unused code out of your final deliverable. Speed is a UX feature in Angular 6. This is accomplished with better support for the Angular Component Development Kit (CDK), Angular Material, Animations, and i18n. Angular Universal allows for server-side assisted fast startup times, and Angular PWA support takes advantage of native platform features such as caching and offline storage, so in subsequent visits, your app remains fast. RxJS 6 support allows the tree-shakeable pipe command, reducing bundle sizes more often, and fixes the behavior of throttle
as I caution you in Chapter 6, Forms, Observables, and Subjects, among numerous bug fixes and performance improvements. TypeScript 2.7 brings in better support for importing different types of JavaScript packages and more advanced features to catch coding errors during build time.
Angular Material 6 added new user controls such as tree and badge while making the library a lot more stable with a slew of bug fixes, completeness of functionality, and theming in existing components. Angular Flex Layout 6 brought in polyfills, enabling Internet Explorer 11 to support CSS Flexbox. This makes Angular apps using Material and Flex Layout fully compatible with the last major legacy browser technology that still persists in enterprises and governments despite leaving mainstream support in January 2018 alongside Windows 8.1 and being superseded 18 times by Microsoft Edge. Angular 6 itself can be configured to be compatible down to IE9 using polyfills. This is great for developers who must support such legacy browsers and still be able to use modern technologies to build their solutions.
Some exciting, new ancillary tooling was also released to enable high-frequency, high-performance, or large enterprise use cases. The Angular ecosystem welcomed the NgRx library, bringing Redux-like reactive state management to Angular based on RxJS. The Nx CLI tool, built by former Angular team members, brings an opinionated development environment setup to Angular, suitable for consultants and large organizations that must ensure a consistent environment. This book follows a similar pattern and aims to educate you in establishing a consistent architecture and design pattern to apply across your applications. Google's Bazel build tool enables incremental builds, so portions of your application that haven't changed don't need to be rebuilt, vastly improving build times for large projects and allowing the packaging of libraries to be shared between Angular applications.
As mentioned in the Preface of this book, this book has been designed to be effective with any new version of Angular. This is an idea that is championed by the Angular team, who wishes to deemphasize the specific version of Angular you're currently using, instead of focusing and investing in continually staying up to date with every minor and major release of Angular. The Angular team is spending considerable energy and effort to ensure that as much of the code you have written remains compatible, as the performance and feature set of Angular improve over time. Any breaking change is either supported by automated tools, helping you rewrite portions of your code, or planned deprecations, giving you ample time to phase out unsupported code.
Angular 7 brought performance, accessibility, and dependency updates for TypeScript, RxJS, and Node, along with a significant update and the expansion of Angular Material controls; Angular 8 continuous these trends. Angular 8 introduces differential loading and support for minimal polyfills for evergreen browsers, saving somewhere between 7-20% of the payload delivered to the client.
Angular 9 and its subsequent 9.1 update brings some of the most significant updates to the framework to date by delivering the Ivy rendering engine and TypeScript 3.8 support. This update tackles a lot of tech debt removal, brings 100 bug fixes and features, and greatly expands automated test coverage of the framework. The Ivy rendering engine results in smaller package sizes and faster load times for your apps. In addition, Angular 9.1 brings 40% faster build times, 40-50% improved unit test run-times, and better debugging capabilities with simpler stack traces and template binding. TypeScript 3.8 brings in new syntactical benefits like optional chaining and the nullish operator to make it dramatically easier to deal with null or undefined values in Angular's strict mode.
The full benefits of the Ivy rendering engine will be felt with future updates. Ivy will allow the creation of tiny and lean Angular applications. Prior to Ivy, the metadata needed to describe an Angular component was stored within a module. With Ivy, components implement the locality principle, so they can be self-describing. This allows Ivy to lazily load individual components and creation of standalone components. Imagine an Angular library that can render components with a single function call and only be a few kilobytes in size. This miniaturization makes it feasible to implement Angular Elements using the Custom Elements, part of the Web Components spec.
Angular Elements, introduced in version 6, allows you to code an Angular component and reuse that component in any other web application using any web technology, in essence declaring your very own custom HTML element. These Custom Elements are cross-compatible with any HTML-based tool-chain, including other web application libraries or frameworks. To make this work, the entire Angular framework needs to be packaged alongside your new custom element. This was not feasible in Angular 6, because that meant tacking on at least 65 KB each time you created a new user control.
In early 2020, Chrome, Edge, and Firefox support Custom Elements natively, a significant change from the status quo in early 2018. Angular 9 enables the Ivy rendering engine by default, and future updates to Angular should drive base bundle sizes to be as small as 2.7 KB, so wide-spread use of Angular-based Custom Elements could soon become reality. In 2020, all major browsers natively support Custom Elements, leaving Safari the last browser implement the standard.
Always check https://caniuse.com before getting too excited about a new web technology to ensure that you are indeed able to use that feature in browsers that you must support.
Angular.io leverages Custom Elements to demonstrate the feasibility of the technology. The documentation site attracts 1 million+ unique visitors per month, so it should help work out some of the kinks as it matures. Custom Elements are great for hosting interactive code samples alongside static content. For example, in early 2018, Angular.io started using StackBlitz.io for interactive code samples.
StackBlitz.io is an amazing tool, a rich IDE right in the browser, so you can experiment with different ideas or run GitHub repositories without needing to pull or execute any code locally.
Other significant updates include the differential loading of JavaScript bundles to improve loading times and time-to-interactive (TTI) for modern browsers. Angular Router adds backward compatibility to make it feasible to perform piecemeal upgrades of legacy AngularJS projects.
Google mandates that the 2000+ Angular projects they have must all be on the same version of Angular. This means that every new update to Angular is well tested and there are no backward compatibility surprises.
With all the groundwork laid in version 9, we can expect a more agile and capable framework with Angular 10. I hope you are as excited as I am about Angular and the future possibilities it unlocks. Buckle up your seatbelt Dorothy, 'cause Kansas is going bye-bye.
In summary, web technologies have evolved to a point where it is possible to create rich, fast, and native web applications that can run well on the vast majority of desktop and mobile browsers that are deployed today. Angular has evolved to become a mature and stable platform, applying lessons learned from the past. It enables sophisticated development methodologies that enable developers to create maintainable, interactive, and fast applications. Technologies like TypeScript, RxJS, and NgRx enabled patterns from object-oriented programming, reactive programming, and the Flux pattern.
Angular is engineered to be reactive through and through and, therefore, you must adjust your programming style to fit this pattern. In addition, Angular is meant to be consumed in an evergreen manner, so it is a great idea always to keep your Angular up to date.
Leveraging promises in an Angular app, instead of observables and the async pipe, is equivalent to disregarding all the advice and documentation that the Angular team and thought leaders in the community have communicated. It is easy to fall into bad practices and habits following shallow or wildly out-of-context advice you may glean from self-help sites or blog posts written with an experimental mindset. The official documentation should be your bible, found at https://angular.io/docs.
In the next chapter, you will be configuring your development environment to optimize it for a great and consistent Angular development experience across macOS and Windows operating systems. In the following chapters, you will learn how to create a basic Angular app, deploy it on the internet, then learn about advanced architectural patterns to create scalable applications, learn how to create a full-stack TypeScript application using Minimal MEAN, and leverage advanced DevOps and Continuous Integration techniques. The book wraps up by introducing you to Amazon Web Services and Google Analytics.
Answer the following questions as best as you can to ensure that you've understood the key concepts from this chapter without Googling. Do you need help answering the questions? See Appendix D, Self-Assessment Answers online at https://static.packt-cdn.com/downloads/9781838648800_Appendix_D_Self-Assessment_Answers.pdf or visit https://expertlysimple.io/angular-self-assessment.
fromEvent
function. Determine if the mouse was double-clicked within a 250ms timeframe using throttleTime
, asyncScheduler
, buffer
, and filter
operators. If a double-click is detected, display an alert in the browser. Hint: Use https://stackblitz.com or implement your code and use https://rxjs.dev/ for help.