Effective Angular - Roberto Heckers - E-Book

Effective Angular E-Book

Roberto Heckers

0,0
28,79 €

-100%
Sammeln Sie Punkte in unserem Gutscheinprogramm und kaufen Sie E-Books und Hörbücher mit bis zu 100% Rabatt.

Mehr erfahren.
Beschreibung

Angular is an open-source, front-end framework used to build web applications using TypeScript. Angular is a component-based framework, which means you build apps by developing and composing reusable components. This framework has all the built-in features needed to develop complex, feature-rich web apps. Written by an Angular specialist working with some of the top companies in the Netherlands, this book teaches you how to harness the full potential of the Angular framework.
You’ll explore different front-end architecture designs and set up a scalable environment for Angular applications and libraries using Nx, before taking a deep dive into the framework's newest and most powerful features. Next, you’ll learn to manipulate the Document Object Model (DOM) with Angular directives, pipes, and animations, and build reusable components like a pro. The book spotlights best practices and flags potential pitfalls at every step of the way. You’ll also learn design patterns that fit well when using the Angular framework, reactive programming with RxJS and Angular signals, and how to manage application states effectively. The book teaches you about accessibility, testing, and optimizing your app for deployment.
By the end of this book, you’ll be able to use Angular effectively to build enterprise-ready, scalable front-end applications.

Das E-Book können Sie in Legimi-Apps oder einer beliebigen App lesen, die das folgende Format unterstützen:

EPUB
MOBI

Seitenzahl: 641

Veröffentlichungsjahr: 2024

Bewertungen
0,0
0
0
0
0
0
Mehr Informationen
Mehr Informationen
Legimi prüft nicht, ob Rezensionen von Nutzern stammen, die den betreffenden Titel tatsächlich gekauft oder gelesen/gehört haben. Wir entfernen aber gefälschte Rezensionen.



Effective Angular

Develop applications of any size by effectively using Angular with Nx, RxJS, NgRx, and Cypress

Roberto Heckers

Effective Angular

Copyright © 2024 Packt Publishing

All rights reserved. No part of this book may be reproduced, stored in a retrieval system, or transmitted in any form or by any means, without the prior written permission of the publisher, except in the case of brief quotations embedded in critical articles or reviews.

Every effort has been made in the preparation of this book to ensure the accuracy of the information presented. However, the information contained in this book is sold without warranty, either express or implied. Neither the author, nor Packt Publishing or its dealers and distributors, will be held liable for any damages caused or alleged to have been caused directly or indirectly by this book.

Packt Publishing has endeavored to provide trademark information about all of the companies and products mentioned in this book by the appropriate use of capitals. However, Packt Publishing cannot guarantee the accuracy of this information.

Group Product Manager: Kaustubh Manglurkar

Publishing Product Manager: Chayan Majumdar

Book Project Managers: Shagun Saini and Arul Viveaun S

Senior Editor: Anuradha Joglekar

Technical Editor: K Bimala Singha

Copy Editor: Safis Editing

Proofreader: Anuradha Joglekar

Indexer: Rekha Nair

Production Designer: Prashant Ghare

DevRel Marketing Coordinators: Mouli Banerjee and Nivedita Singh

Publication date: August 2024

Production reference: 2160824

Published by Packt Publishing Ltd.

Grosvenor House

11 St Paul’s Square

Birmingham

B3 1RB, UK.

ISBN 978-1-80512-553-2

www.packtpub.com

Contributors

About the author

Roberto Heckers is an Angular specialist who has been using the framework since the AngularJS days. He has extensive experience under his belt with the Angular framework. His journey started as a self-taught software engineer building applications with AngularJS. In 2019, he was a front-end team lead for the first time, building an extensive real-estate application and UI library for Spacewell. Since then, he has been focused on building enterprise applications with complex architecture and scalability needs. Roberto now works as a freelance Angular engineer for some of the biggest companies in the Netherlands, such as Vattenfall and Marel, building applications used by millions of users.

About the reviewers

Sarath Raj, a seasoned professional with over 15 years of experience, specializes in software design, development, and maintenance across diverse sectors such as business-to-business, trading platforms, and customs border management. With proficiency in Reactive microservice architecture, cloud-native apps, DevOps, and mobile apps, he is recognized for his innovative product architectures and adeptness in concurrent project execution. He is deeply passionate about application security, Rust, and Solana blockchain, and his expertise spans Java, Angular, Scala, Kotlin, distributed systems (Akka), the cloud (Azure and Google), and data analytics (Azure and Google).

Rubén Peregrina is a lead frontend engineer hailing from Barcelona, Spain. With over seven years of experience, he is an expert in Angular and has worked in various sectors, such as e-commerce, marketing, healthcare, and fintech. Rubén started his career as a full stack developer, working with AngularJS and Java. He then went on to explore other technologies, such as Vue.js and Flutter. In recent years, he has focused primarily on Angular, working on international projects. Aside from his work, Rubén is an active contributor to different projects on GitHub and has even started his own blog. Through his blog, he shares his expertise in frontend development, Angular, TypeScript, and JavaScript.

Table of Contents

Preface

Part 1: Angular Basics and Setting Up Scalable Nx Workspaces

1

Scalable Front-End Architecture for Angular Applications

Technical requirements

Understanding scalable front-end applications

What is scalability?

Why is front-end architecture important?

Different approaches to scaling front-end applications

Monorepo or polyrepo

Architectural patterns for Angular applications

Design patterns used within Angular applications

What is Nx and why should you use it?

How Nx helps you build Angular monorepos that scale

Setting up a scalable Angular workspace

What did Nx create in our new workspace?

Improving our Nx workspace for better Angular development

Structuring Angular applications and libraries

Do you create a new library or reuse an existing one?

Summary

2

Powerful Angular Features

What makes Angular so powerful?

New features in the Angular framework

Standalone components

Dependency injection using the inject function

Directive composition

Angular Signals

Angular control flow

Other noteworthy new Angular features

A deep dive into Angular routing

Creating new components

Configuring routes in Angular applications

Route configuration options

Component communication

Receiving values with the @input() decorator

Emitting values with the @Output() decorator

Two-way data binding using @Input and @Output

Other component communication methods

Dependency injection

Providing dependencies

Injecting dependencies

Using the inject function for better dependency injection

Dependency instances, injector hierarchy, and resolution modifiers

Summary

3

Enhancing Your Applications with Directives, Pipes, and Animations

Using and creating Angular directives

Angular attribute directives

Angular structural directives

Directive selectors

Angular directive composition

Transforming values using Angular pipes

Using pipes in HTML templates and TypeScript files

Is it pure or impure?

Using AsyncPipe

Building your own pipes

Creating and reusing stunning animations

Summary

4

Building Forms Like a Pro

Understanding the different types of forms in Angular

Building template-driven forms

Creating a forms library with a form component

Creating a template-driven form

Building reactive forms

Creating a reactive form

Creating forms dynamically

Summary

Part 2: Handling Application State and Writing Cleaner, More Scalable Code

5

Creating Dynamic Angular Components

A deep dive into Angular content projection

Creating a modal component using content projection

Exploring multi-slot content projection with ng-content select

Displaying projected content multiple times or conditionally

Using template references and variables

Using template variables effectively

Using TemplateRef elements effectively

Rendering components dynamically

Rendering components dynamically using ngComponentOutlet

Using an injector with ngComponentOutlet

Lazy loading dynamic components

Rendering components dynamically using the defer control flow

Summary

6

Applying Code Conventions and Design Patterns in Angular

Exploring commonly used code conventions and best practices in Angular applications

Naming conventions

Structural conventions

Using best practices in Angular applications

Exploring commonly used design patterns in Angular applications

Creational design patterns in Angular

Structural design patterns in Angular

Behavioral design patterns in Angular

Building a generic HTTP service containing a model adapter

Using the generic HTTP service

Providing mock data for our API requests

Summary

7

Mastering Reactive Programming in Angular

What is reactive programming?

Highlights of reactive programming

Drawbacks of reactive programming

Reactive programming in Angular

Reactive programming using RxJS

What are Observables?

Unsubscribing from Observables

Using and creating RxJS operators

Types of operators

Flattening multiple Observable streams using flattening operators

Powerful and useful RxJS operators

Creating combined and reusable RxJS operators

Reactive programming using Angular Signals

Using Signals, computed Signals, and Signals effects

Combining Signals and RxJS

Using toSignal

Using toObservable

Choosing between Signals and RxJS

Summary

8

Handling Application State with Grace

Understanding application state

Different levels of application state

Fundamental concepts within state management

Handling global application state using RxJS

Building a state management solution using RxJS

Connecting your state management and view layer with a facade service

Handling global application state using Signals

The problem with using RxJS or Signals for global state management

Handling global application state with NgRx

Installing the @ngrx/store and @ngrx/effects packages

Defining your first NgRx actions

Creating your first NgRx effect

Creating your initial state and first reducer functions

Defining NgRx selectors

Adjusting the facade service so that they use NgRx state management

Adding additional actions, effects, reducers, and selectors

Summary

Part 3: Getting Ready for Production with Automated Tests, Performance, Security, and Accessibility

9

Enhancing the Performance and Security of Angular Applications

Understanding Angular change detection

Zone.js and Angular

Improving change detection efficiency

Angular change detection and Signals

Enhancing the performance of Angular applications

Understanding and using the runOutsideAngular() method

Understanding and using the NgOptimizedImage directive

Understanding and using the trackBy and track functions

Understanding and using web workers in Angular

Building secure Angular applications

Setting up route guards

Angular attack surfaces and how to mitigate them

Summary

10

Internationalization, Localization, and Accessibility of Angular Applications

Adding translatable content in Angular applications

Installing Transloco in your Nx monorepo

Translating content using Transloco

Changing the Transloco configurations at runtime

Localization for Angular applications

Localizing currencies using the translocoCurrency pipe

Localizing dates using the translocoDate pipe

Localizing numbers using the translocoDecimal pipe

Making your Angular applications accessible to everyone

How to make Angular applications accessible

Using the tabindex attribute

Adding ARIA attributes

Summary

11

Testing Angular Applications

Different types of application testing

Understanding unit tests

Understanding end-to-end tests

Understanding component tests

Understanding integration tests

Unit testing of Angular applications using Jest

Setting the coverage threshold

Adding additional configurations

Writing and running unit tests

Adding additional unit tests for the expenses-registration application

End-to-end testing of Angular applications using Cypress

Writing your first e2e test

Defining page objects for e2e testing

Using fixtures in your e2e tests

Summary

12

Deploying Angular Applications

Building and linting Angular applications inside your Nx monorepo

Linting Nx projects

Building your Angular libraries and applications

Analyzing your build output

Automatically deploying Angular applications

Creating an access token

Creating a .yml file

Deploying the application to GitHub Pages

Fixing workflow fails

Summary

Index

Other Books You May Enjoy

Part 1:Angular Basics and Setting Up Scalable Nx Workspaces

In this part, you’ll learn about frontend architecture and what to consider when creating your workspace. Additionally, you’ll set up your own Nx monorepo, which is ready to handle hundreds of Angular applications and libraries. After creating your Nx monorepo, you’ll learn about the newest features in the Angular framework, such as signals and standalone components. When you’re fully up to date with the latest developments of the Angular framework, you’ll do a deep dive into the Angular router, component communication, and dependency injection. Then, you will get hands-on experience with Angular directives, pipes, and animations and learn about the best practices, their impact on performance, and common pitfalls when using directives, pipes, or animations. You will finish the first part by creating template-driven, reactive, and dynamic forms, understanding their differences when you use each method and how you can adequately validate them.

This part includes the following chapters:

Chapter 1, Scalable Front-End Architecture for Angular ApplicationsChapter 2, Powerful Angular FeaturesChapter 3, Enhancing Your Applications with Directives, Pipes, and AnimationsChapter 4, Building Forms Like a Pro

1

Scalable Front-End Architecture for Angular Applications

Angular is a powerful and extensive framework for building web applications. According to the 2023 Stack Overflow developer survey, it is the fourth most used web technology after ReactJS, NodeJS, and jQuery among professional developers. Due to the structure and tools it provides, Angular is often chosen when building large web applications or enterprise solutions comprised of several applications and libraries.

This book will guide you through the process of effectively using the Angular framework to develop and test applications of any size. You’ll start by learning about front-end architecture and setting up a scalable workspace with Nx that’s ready for hundreds of Angular applications. Next, you’ll explore the most powerful and newest features within the Angular framework. You’ll learn about reactive programming and state management using RxJS, Signals, and NgRx, and will be able to test Angular applications with Jest and Cypress. Upon completing this book, you’ll be able to effectively use the Angular framework and develop scalable, enterprise-ready Angular applications, utilizing all the tools Angular has to offer while implementing best practices and sound design patterns.

In this chapter, you will create your Angular workspace using Nx. You’ll start by learning what we mean by scalable front-end architecture and why it is essential to think about your architecture before you start writing code. You will also learn about different patterns in front-end architecture and what to consider when building enterprise-ready solutions from scratch.

Lastly, you will explore and use Nx, the build tool that allows you to create scalable Angular monorepos. By the end of this chapter, you will understand crucial aspects of front-end architecture and have your own Nx monorepo for Angular, ready to handle hundreds of applications easily.

This chapter will cover the following main topics:

Understanding scalable front-end applicationsDifferent approaches to scalable front-end architectureWhat is Nx and why should you use it?Setting up a scalable Angular workspace

Technical requirements

By the end of this chapter, we will create an Nx monorepo with an Angular application and library in it. To follow along, you’ll need to install some tools. Note that we will only use freely available tools.

You will require the following:

Visual Studio Code (VS Code) as your integrated development environment (IDE)Chrome web browserAngular 17.1 or higherNodeJS version v20.11.0 or higherTypeScript version 5.3.3 or higherNx version v18.0.7 or higher

Throughout this book, we will use Angular 17.1, NodeJS 20.11.0, TypeScript 5.3.3, and Nx 18.0.7.

The GitHub repository for this book is available at https://github.com/PacktPublishing/Effective-Angular.

Understanding scalable front-end applications

Modern web applications are constantly getting bigger and more complex. Because of this, developing scalable front-end applications is more critical than ever. To create scalable front-end applications, we need to understand what scalability means in the context of a front-end application.

What is scalability?

The first thing that might come to mind when you hear the term scalability is handling more traffic. However, in the context of front-end applications, when talking about scalability, we mostly mean the scalability of the code base. Or, more concisely, the code is easy to extend, and modules or micro front-ends can be added to the software without much work. Components and libraries are reusable; the code is easy to maintain, test, and debug, even when the application grows. You can work with different teams on separate parts of the application, and onboarding new teams that write similar code is easy to achieve and enforce. The application has good performance and small bundle sizes. Compile and build times remain low, and deployment can be done swiftly to different staging environments if needed.

To achieve these feats within your front-end applications, you must create a good architecture, a set of tools, and rules everyone can adhere to. Your architecture will include elements such as your repository type, folder structure, architectural patterns, design patterns, a programming language, framework, and tools for building, testing, linting, and deploying your application. Making the right decisions for each part of your architecture helps with creating scalable applications that are easy to maintain and extend.

When making architectural decisions, you should aim to create a fast, loosely coupled, testable system. You want to avoid direct dependencies between different parts of your system so that you don’t get stuck and need to refactor the entire application when the business introduces changes.

With this brief introduction to what a scalable front-end application is, let’s understand the importance of a good front-end architecture.

Why is front-end architecture important?

Good architecture is essential to maintain a good workflow for the developers working on the software and makes it easy for new teams and team members to join. If developers spend much time refactoring or waiting for builds or tests to complete, they wander off and do less productive things.

With good architecture in place, the code base remains manageable, even when your applications grow. Without good architecture, the code becomes messy and hard to debug or extend. As time progresses, such problems will pile up, and developers will be wasting more time on bugs and refactoring than creating new features, especially in large enterprise solutions where business needs are constantly changing. Before you know it, you will have a web of dependencies, and adding simple things will become very time-consuming.

Suppose you’re building an application for employee scheduling that includes a calendar component. You want to avoid tight coupling between the calendar and scheduling applications. When management lets you know the company is adding another application – let’s say a project management tool that also includes a calendar – you don’t want to redo the entire calendar component and scheduling application because the two are tightly coupled.

The following figure shows the development process with and without architecture. Without architecture, you start fast, but in the end, you are crawling. With architecture, you will have a consistent and predictable work pace:

Figure 1.1: Development speed

Now that you understand what we mean by scalable front-end applications and why having good architecture is essential to achieve them, we will dive into some approaches to scaling front-end applications and learn about their advantages and trade-offs.

Different approaches to scaling front-end applications

When it comes to architecting software, it’s essential to think of what you are building, what it can grow into, what the context is, and who will work on it. Depending on these parameters, you’ll want to create an architecture that’s flexible enough to grow and adapt without overengineering it, making things more complex and time-consuming than needed.

For example, if you’re building a simple website for a small family-owned business, you don’t need elaborate architecture and complex design patterns; this will make things more complex and time-consuming than they need to be. The needs for the website will probably stay mostly the same, and the code base will remain small and manageable. But when you’re building enterprise software comprised of multiple applications, the needs and utility of those applications will change quite a bit, and you’ll want to ensure that the software is set up for that.

In this section, you will learn about different architectural choices so that you can ensure you don’t over or under-engineer your front-end applications. You will learn about different repository structures and architectural patterns that are commonly used within Angular applications. Without further ado, let’s learn about the differences between mono and poly repositories.

Monorepo or polyrepo

Monorepo and polyrepo are two options for storing code within a source control application such as GitHub. If you create a new repository for each project or application, it’s called a polyrepo or multi-repos structure. Meanwhile, when all applications are in one large repository, it’s called a monorepo structure. For large companies, a monorepo can easily contain hundreds of applications.

What option should you pick when developing a large front-end environment? Let’s start exploring the advantages and disadvantages of both solutions.

Advantages of monorepos

Some of the key advantages of monorepos are as follows:

Easy code sharing: Monorepos make it easy to share code among different projects and teams. You have the code of all projects in one directory, so inspecting or reusing code from another project and sharing things in libraries is simple. This helps prevent duplicate code, gives you inspiration and guidance on solving similar issues, and allows you to quickly check if a bug originates in your code or the implementation of a library.Uniformity among different applications: Because all code resides in one repository, enforcing uniformity among different applications is easier. You can ensure that the same tooling is used, code conventions and best practices are applied, and all applications are tested similarly. Also, testing the entire system is easier with a monorepo.No versioning conflicts: In a monorepo, dependencies commonly share the same version among all applications. This ensures that no versioning conflicts occur and implementations are equal in all applications. Furthermore, it ensures that applications that are not actively developed still get regular updates for their dependencies.Cross-project refactoring: If you want to refactor something that occurs in many applications, you can do it in one go for all applications or efficiently run scripts to perform these actions.Quick code movement and debugging: Code can quickly be moved from one project to another, and when you encounter a bug within a library, you can fix the bug without holdups and continue your work.Shared commit timeline: Lastly, a monorepo has one shared commit timeline. This makes it safe to create atomic commits with changes in multiple applications. This happens because if anything goes wrong, you can always revert to a common state of all applications through the commit timeline.

Regardless of these advantages, monorepos have some shortcomings too. We’ll look at them in detail in the following section.

Disadvantages of monorepos

Some of the disadvantages of monorepos are as follows:

Large folder structure: A monorepo can be daunting because all code and libraries live in one large solution. If you have yet to work with a monorepo, this might take some time to get used to. Because it’s so easy to use code from other projects, you need to be extra careful not to create unwanted dependencies between applications.Complex package updates: Updating packages and dependencies in a monorepo can be complicated because, often, all projects need to update at once.Breaking changes: Because there are no different versions, when you make changes in libraries, you can break other applications without noticing it.Slow build times: In a monorepo, building and testing can become time-consuming if not managed correctly.Challenges in deployment: Deploying applications modularly can be more challenging compared to polyrepos, where everything is separated and modular by nature.

Most disadvantages can be mitigated with the right tools. For monorepos comprised of Angular applications, you can use Nx, giving you everything to handle a monorepo without these disadvantages. In this book, we will work with a monorepo and Nx.

Now, let’s move on to the advantages and disadvantages of polyrepos.

Advantages of polyrepos

The key advantages of polyrepos are as follows:

A higher level of isolation between applications: The most apparent advantage of a polyrepo is higher levels of isolation between your applications. Each project has a repository and developers can do whatever they want within that repository without affecting other projects too much.Flexibility with dependency management: When using a polyrepo, each project can manage versions of its dependencies on its own. This gives the teams working on the projects more freedom when updating dependencies and offers more stability.Individual tooling: With a polyrepo, there is more flexibility for using different tools and programming languages.Easy to manage: A polyrepo is generally easier to manage, especially for smaller teams. There is little code in each repository compared to a monorepo, so there are fewer things you can affect with your changes.Straightforward modular deployment: Deploying your applications modularly is more straightforward.In-line with a micro front-end architecture: Lastly, if you’re developing with a micro front-end architecture, using a polyrepo might feel more in tune with the rest of your architecture. However, micro front-ends can be achieved with both a monorepo and polyrepo structure.

Before you pick between a monorepo or polyrepo structure, you must also consider the drawbacks of polyrepos. These are described in the next section.

Disadvantages of polyrepos

Some common disadvantages of polyrepos are as follows:

Difficulties in code sharing: First, because code resides in isolation, it is harder to share code between applications. This can result in different implementations for the same problem or duplicate code in various projects. Teams will often create their own solution instead of contributing to a library that solves shared problems.Boundaries between projects and libraries: When you depend on a library that resides in another repository, and you run into a bug, it will be more time-consuming to fix the issue. Often, you need to wait for other teams to fix the bug in the library and deploy a new version before you can continue.Challenges when testing multiple repositories: Testing applications can become more challenging. Predominantly when applications consist of different modules that reside in different repositories, testing the entire system can be difficult.Difficulties in creating CI/CD pipelines and deployments: Creating CI/CD pipelines and deployments of the whole system might become more challenging as you need multiple repositories to complete the tasks.Dependency conflicts: Lastly, you can run into dependency conflicts. Different applications need to work together in production and can depend on similar dependencies. You can encounter compatibility issues when these applications use different versions of the set dependency.

In this section, you learned about the advantages and disadvantages of storing your code in a monorepo or polyrepo. Next, you will learn about different architectural patterns that are commonly used with Angular applications.

Architectural patterns for Angular applications

Architectural patterns focus on how you structure your code and provide rules for abstracting business logic away from specific implementations. Architectural patterns are an extensive topic and there are entire books dedicated to it; so, it will be impossible to cover everything in this section. Still, we will briefly cover some of the most common architectural patterns that are used with Angular and Nx. In Chapter 5, we will dive into design patterns while focusing on code implementations instead of providing a high-level view of the system.

Now, let’s dive into some architectural patterns and learn what they try to achieve, how they try to achieve it, and what their advantages and disadvantages are.

Common architectural patterns in Angular applications

Most architectural patterns try to accomplish the same at their core, only with little nuances and different terminologies. Regarding architectural patterns for Angular applications, they try to separate domain and business logic from implementations and the view. Doing so gives you a loosely coupled system that is easy to test, change, and expand without creating dependencies in the wrong places. In this section, we will cover the Model-View-Controller (MVC), hexagonal architecture, and layered architecture. These three patterns are some of the most commonly used architectural patterns for Angular applications. Other noteworthy patterns are the Model-View View-Model (MVVM), union architecture, and clean architecture.

Without further ado, let’s learn about the MVC pattern and how it can be used within Angular applications.

MVC in Angular applications

MVC is one of the most commonly used architectural patterns in the world of software development. The pattern was first used in the back-end but is now also used for front-end applications, or at least something resembling the MVC pattern. The MVC pattern is often used for Angular applications because it fits well with the tools provided by the framework. As the name implies, MVC consists of three parts – the model, the view, and the controller:

The model declares the data models and handles business and data-related logic.The view displays the current state of the model to the user.The controller acts as a bridge between the view and the model. The controller passes the user input to the model so that the model can perform actions and update accordingly, after which the controller returns the updated values to the view.

If we translate this pattern into an Angular application, the model would be a service handling the data and data models. The view would be the HTML template. Finally, the controller would be the TypeScript file behind the HTML file, commonly named the component class. A better implementation is considering the component class as part of the view and having an extra facade service (an additional abstraction layer creating a simple interface and communication layer between the view and business or state-related logic of your application; we will discuss the facade pattern in more detail in Chapter 5) between the model service and the view acting as the controller.

If you keep true to the MVC architecture’s original implementation, the model directly gives the updated values to the view. To achieve this, in Figure 1.2, we would eliminate steps 4 and 5 and would then go to the view instead of the controller. In Angular, this is sort of the case when using the component class as a controller, and it results in a tight coupling between the business logic and the component classes. Because of that, I prefer to add a separate facade service that acts as a controller to fully separate the component classes from the business and state layers of my application. By separating the components from the state and business logic, you end up with a loose coupling, making it easier to change implementations throughout your application:

Figure 1.2: MVC pattern

Now that you know what the MVC pattern entails and how you can implement it within your Angular applications, let’s learn about the next common architectural pattern: the Hexagonal architecture.

The hexagonal architecture pattern in Angular applications

Hexagonal architecture is relatively new compared to MVC and some other architectural patterns, such as layered architecture, MVVC, and MVP. The hexagonal architecture was introduced in 2005, and people have only started implementing it in Angular applications in the last couple of years. It gained popularity because domain-driven development (DDD) became a hot topic, and hexagonal architecture is very well suited to combine with DDD. The main principle of hexagonal architecture is to separate the core application logic away from the UI and data implementation through ports and adapters. Because of that, the architecture is also commonly referred to as the ports and adapter architecture. But I hear you thinking, what are ports and adapters?

In simple terms, ports are interfaces (or abstract classes) that separate your core logic from the UI and code implementations. These interfaces dictate how the UI and code implementations can communicate with your application core. The adapters are the UI and code implementations that connect with your application core through the ports. In hexagonal architecture, ports and adaptors come in two types, UI and data-related ports and adapters – in other terms, primary and secondary adapters and ports. This concept is illustrated in Figure 1.3:

Figure 1.3: Hexagonal architecture pattern

When implementing hexagonal architecture, you will have a set of ports and adapters for each domain within your application. I like to use a facade service between the port interfaces and adapters for more abstraction between the UI, implementations, and application core. When using a facade service, the facade can be considered as the port itself. Just make sure the facade implements an interface so that there is a fixed set of rules for communication with the core and adapters.

Because ports define a fixed set of rules for communicating with your application core, you can easily change implementations when business requirements change. You can swap UI components without touching your business logic or data implementations, and you can change how you persist or fetch your data without touching your views or application core. The only thing you need to do is ensure your new implementation can connect to the same interface your ports are using to connect everything. This approach offers excellent flexibility and a loosely coupled system.

To clarify things, I want to go over Figure 1.3 and translate it into an Angular application. We will go from left to right. On the far left, we have the primary adapters. Everything the user faces, or triggers, is considered a primary adapter: components, directives, resolvers, guards, and event listeners. One step to the right, we will find the primary ports. These regular TypeScript interfaces (or facade services) dictate how the UI layer communicates with the application core. Our application core is in the middle, where we access state management and define business and application logic in Angular services. On the right of the application core, we have our secondary ports. These ports dictate how the application code communicates with HTTP services, state management, in-memory persistence, event dispatchers, and other data or API-related logic. Like the primary ports, the secondary ports are regular TypeScript interfaces (or facade services). On the far right, we have our secondary adapters. The secondary adapters implement our HTTP services, local storage persistence, state management, and event dispatchers.

Now that you know what hexagonal architecture is and how you can implement it within your Angular applications, let’s take a look at the third and final architectural pattern we will discuss: layered architecture.

The layered architecture pattern in Angular applications

As the name implies, the layered architecture pattern uses different layers to separate concerns. For each application section, you create a layer in the architecture that sits on top of another layer. When you implement the layered architecture pattern in your Angular applications, you should have at least three (main) layers: the core layer, the abstraction layer, and the presentation layer. Within these top-level layers, you can have additional sub-layers or sibling elements. If your application architecture needs more layers, you can add them as needed.

The most important thing with layered architecture is that each layer can only communicate with the layer above and below itself; the rest of the layers in the chain are off limits. Another essential feat is that events and actions flow upwards, and data flows downwards. The user triggers an event or performs an action in the presentation layer. This layer notifies the abstraction layer of the action and the corresponding changes. The abstraction layer sends these actions and changes to the core layer, where the business logic is performed and the data changes persist. When the core layer has performed the application logic and persisted the changes, the data will flow back from the core layer through the abstraction layer in the presentation layer, where the view is updated for the user:

Figure 1.4: Layered architecture pattern

Throughout this book, we will use a layered architecture resembling what’s shown in Figure 1.4, with the presentation layer containing dumb, wrapper, and smart components. Dumb components only have component inputs to receive data and outputs to alert the parent components something has changed. Wrapper components are also dumb, but they are used to group multiple components and provide a reusable layout or animation. Even though a wrapper container can wrap around dumb components, they are the same for the flow of data and separation of dependencies, which is why they are placed next to each other in the architecture design. On top of the dumb components, we have smart components. These components are, generally speaking, specific business use cases or pages, and they inject facade services and implement component logic and state.

The next main layer in our architecture is the abstraction layer, where we have facade services. These facade services are regular Angular services that implement the facade design pattern. These facade services provide additional abstraction and are used as a bridge between our smart components and the core layer of the application.

Our last major layer is the core layer, where the global state management, business and application logic, and HTTP services reside. Our HTTP services layer lies on top of the state management and business logic layers. We have separate services that do nothing but fetch data and pass it to our other core layers; the lower core layers never fetch data directly, so we have an additional abstraction layer and have better separation of concerns.

Now that you know about the layered, hexagonal, and MVC architectural patterns, let’s move on and briefly learn about the advantages and disadvantages of each

Comparing the architectural patterns

All three patterns we discussed in the Common architectural patterns in Angular applications section separate the business logic from the implementations and presentation code. They all provide abstraction layers but have different approaches to creating these layers. First, the MVC pattern might seem the simplest to implement, but you can have too many dependencies and implementations in your component classes if you don’t add facade services, especially if you let the model part of MVC directly communicate with the view. Not only will this tightly couple your view with your business logic, but it can also trigger extensive DOM updates. Changing implementations can become hard, and unit testing needs a lot of mocking.

Next, we have hexagonal architecture. I like this architecture, but it introduces a lot of boilerplate code and can feel complex to implement, making it not the right fit if you have a lot of junior developers on your team. Nonetheless, the significant advantage of hexagonal architecture is that you can easily change implementations once everything has been set up. Unit testing the code also becomes straightforward because everything is separated into ports and adapters.

Lastly, we have the layered architecture. This one offers the best of both the MVC and hexagonal architectures: we have clear divisions and good abstraction for our core, implementations, and view. Adding more layers to your architecture is simple, and the rules are easy to understand, making it a good solution for teams with developers of different experience levels. Because of good separation and abstractions, you can simply change implementations, and unit testing remains easy.

You now know what the MVC, hexagonal, and layered architectures are, and you learned about the advantages and disadvantages of each implementation. In the next section, you’ll briefly learn what design patterns are and how they differ from architectural patterns.

Design patterns used within Angular applications

While architectural patterns focus on a high-level overview of how we segment and abstract our code, design patterns focus on how we implement things within our code. When developing Angular applications, we are already working with some design patterns out of the box. This is because Angular is a strongly opinionated framework with strong object-orientated programming (OOP) principles at its core. Some design patterns we use by default within Angular applications are the observable, dependency injection, decorator, component, and singleton patterns. These and other design patterns, such as the factory and inheritance patterns, are embedded in how the Angular framework works and should be used throughout your application when you use the framework correctly. Because these patterns are somewhat concealed within the tools and ways of working of the Angular framework, you might be using them without actually understanding how they work at their core. Besides these design patterns embedded within the Angular framework, you can improve your code by introducing even more design patterns. Some work very well in combination with Angular, such as the facade pattern.

As with architectural patterns, design patterns ensure that you adhere to specific rules when implementing your code, resulting in code that is easy to adjust and extend. They prevent you from making the wrong dependencies throughout your code and provide a structured and battle-tested way to approach common problems with software engineering. For now, I wanted to briefly explain design patterns and list some design patterns the Angular framework uses by default. In Chapter 5, you will learn about design patterns in more detail. You will learn about different patterns, when to use them, and how to implement them correctly.

You now know what design patterns are and how they differ from architectural patterns. You’ve learned about some patterns that are used within Angular by design and that you can add more patterns to improve your code implementations throughout your code base. The next topic we will discuss is Nx, which makes structuring, creating, maintaining, and testing large Angular monorepos easy.

What is Nx and why should you use it?

In this section, you will learn what Nx is and why it’s such a fantastic tool for developing Angular applications at scale. Nx is rapidly becoming the go-to tool for developing large monorepo front-end applications. So, what exactly is Nx and why should you use it?

Nx is a tool that helps you to speed up, streamline, and standardize your development, testing, build, and deployment process. The Nx tooling offers various features and integrations you can utilize during every stage of development. Nx was created so that you can adopt it incrementally by picking and choosing what you want to use or add to your current environment. At its core, Nx helps you with the following:

Speeding up the build and test times of your applications.Managing dependencies and running tasks within monorepo projects.Swiftly scaffolding new code snippets, applications, and libraries without needing to worry about configuring build tools.Integrating new tools into the projects of your monorepo workspace.Ensuring uniformity and consistency within the code of different projects.Updating applications and tools through automated code migration.

In the preceding tasks, multiple tools, features, and options, such as a CLI, generators, and plugins, help you achieve your goals and streamline processes. The tools and features Nx has to offer are divided into different modules in their ecosystem.

To start, you have the Nx command-line interface (CLI). Similar to the Angular CLI, it lets you run commands for tasks such as creating workspaces, scaffolding projects, testing projects, or serving and building. Next, the Nx package contains all the fundamental technologies Nx offers: task running, workspace analysis, build caching, scaffolding, and automated code migrations. Then, there are plugins, which are NPM packages that extend the fundamentals of Nx and can be created by the Nx community for various purposes, such as generating projects, integrating tools, and adding or updating libraries. Another element of Nx is its Devkit, which can be used to build plugins to extend the Nx tooling to your specific needs. Nx also has something called Nx cloud, which speeds up your CI with remote caching and distributed task executions, but this is outside the scope of this book. Lastly, we have the Nx console, an extension for VS Code, IntelliJ, and VIM, making it much easier to manage your Nx workspace and run Nx commands.

Now that you know what Nx does at its core, let’s examine it in more detail and see how it can help you build scalable monorepos for your Angular applications.

How Nx helps you build Angular monorepos that scale

Now that you understand what Nx is at its core, let’s dive deeper and explore how it helps you build, test, and standardize your Angular applications. We will begin with one of the main features Nx offers: speeding up tasks such as building, serving, and testing.

Improving build times with computational caching and incremental builds

Typically, when we run tasks with the Angular CLI, such as ng build or ng serve, our entire application and all the libraries it depends on need to be compiled to complete the build or serve your application. This can become time-consuming as the application grows. The result is slow CI builds and developers waiting for the application to compile each time they want to start or test it. Nx helps to resolve these issues with incremental builds and computational caching.

With computational caching, Nx will check if anything has changed since the last time you ran a command. If nothing has changed or the build computation is equal to a previous cached run, Nx won’t rerun the command and instead take the results from its caching system. First, it will look at the local caching, and if you set up remote caching with Nx cloud, it will also check if it can find the same computational hash in the remote cache. If Nx cannot find the same computational hash, it will run the command and store the hash of the result in the Nx cache.

Besides computational caching, Nx helps speed up our build and compile times with incremental builds. When using incremental builds, we only build projects that have been changed since your last build. In a regular scenario, we build the application and all the libraries the application uses. As the application grows and depends on many libraries, it can become time-consuming and costly to rebuild everything each time you build the application. To use incremental builds, your libraries must be buildable so that Nx can cache the libraries and only build them if they changed since your last build. When you’re building smaller applications, you might not want buildable libraries with computational caching because making a library buildable has some overhead as well. When you create a library in your Nx workspace, you can choose if you want it to be standard, buildable, or publishable. We will dive deeper into this topic in the Structuring Angular applications and libraries section.

Running tasks effectively in a monorepo with Nx

Running tasks, such as ng build, ng test, and ng lint, with the Angular CLI in a single Angular project is straightforward. But things become more complicated when you have a monorepo with tens, hundreds, or even thousands of applications and libraries. In many scenarios, you want to run tasks simultaneously for multiple (or all) projects. Sometimes, you want to run specific tasks when something changes in a project, or you need to know if changes in a library have affected other projects and break them. If you perform these tasks by running commands one by one, this becomes unmanageable quickly, and building tools to watch and check for the affected projects becomes complicated. Luckily, Nx has everything we need to run tasks for multiple or affected projects, watch for changes, and react to them with commands.

When you use the Nx CLI, it is advised not to use the Angular CLI within the monorepo. We want Nx to do all its magic, and this is not possible if we start to generate things with the Angular CLI. Luckily, Nx has you covered!

Let’s start with the basics – running commands for a single project using the Nx CLI. Running tasks for a single project is similar to running tasks with the Angular CLI. For example, if we want to run the tests for an application named testApp, we can run the following command:

nx test testApp

If we want to run tasks for multiple projects, we can use the run-many keyword combined with the -p flag to define the projects for which we want to run the tasks. If you omit the -p flag and only use the run-many keyword, the task will run for all projects. We can also add the -t flag to run multiple tasks at once. For example, if we want to build, lint, and test all projects, we can run the following command:

nx run-many -t build lint test

Now, let’s say we only want to build, lint, and test testApp and testApp2. For this, we can run the following command:

run-many -t build lint test -p testApp testApp2

As you can see, running commands with the Nx CLI is simple, even if you need to do it for multiple or all projects within your monorepo. Even if you want to run several tasks simultaneously for a subset or all projects, you can do it quickly and with a single command.

Another helpful option is to watch for changes in specific projects and run scripts whenever changes occur in a watched project. For example, you can watch for changes in an application and echo the project name and the changed filename. This can be done for single projects, a subset of projects, or all projects, like running regular commands. Here’s an example of what this command will look like:

nx watch --projects=testApp,testApp2 --includeDependentProjects -- echo \$NX_PROJECT_NAME \$NX_FILE_CHANGES

Lastly, we need something to detect the affected projects and run commands for them. When a monorepo grows, it becomes too time-consuming to run tests for all projects every time you change a library. Consider the following scenario. We have five applications and three libraries being used by those applications. Now, if we change the code of a library and all five projects use this library, our changes can affect and break the four other projects we aren’t working on ourselves. This can be a massive problem if we release an application or library to production without noticing we broke another application with our changes. When we make changes in a library, we need to know what projects in the monorepo are affected by those changes and run the appropriate tests to see if everything still works. For this scenario, Nx has affected commands. By running these commands, you can run tasks such as linting and testing for all projects affected by your changes. The following is an example of such a command:

nx affected -t test

With that, you have basic knowledge of running tasks in an Nx monorepo. Next, let’s explore how Nx helps us keep the code and setup in our monorepo projects standardized and uniform.

Ensuring uniformity and consistency within your Nx monorepo

Another critical aspect of a scalable Angular monorepo is uniformity and consistency in the code of the different applications within the monorepo. When you have hundreds of projects in your monorepo, you don’t want different code conventions and implementations in each project. This would make it much harder for developers to start working on other projects within the monorepo, and it also makes it harder to find similar code or refactor code in bulk. When everyone uses the same conventions and code patterns, each developer can work on every project within the monorepo and make changes when needed.

Nx helps us maintain uniformity with global linting rules that apply to all projects within the monorepo. You can set up these linting rules however you see fit and can even create custom linting rules. Besides global linting rules, you can also apply specific lint rules for individual projects.

Nx generators are another great way to enforce uniformity within your Nx monorepo. Generators are used to generate applications, libraries, components, and other code snippets. You can even use Nx generators to modify your code throughout the monorepo or set up and change configuration files. This can be very useful when you need to apply refactoring to multiple files of your monorepo.