Modern Angular
欢迎来到现代 Angular 的世界
本章涵盖内容:
- 阅读本书的预期收获和目标
- 以往
Angular
应用中常见问题的概述 - 现代
Angular
版本针对这些问题提供的新解决方案
Angular
作为最受欢迎的前端开发框架之一,正处于发展的关键节点。
多年来,该框架在性能、用户体验和新功能方面不断改进,例如引入 Ivy 渲染引擎,它不仅减小了捆绑包的大小,还提升了运行时的性能。
这些发展使 Angular
在竞争中占据了有利地位。
如今,社区关注的焦点已不仅仅局限于框架的表面改进,而是更多地转向直接影响用户体验的部分。
同样重要的是,开发者体验相关的方面也受到了关注,比如更好的可扩展性和可组合性等, 这些对于使用该框架的开发者来说尤为重要。
Angular
框架的各个版本在不断提升用户体验的同时,也在持续改善开发者体验,未来的版本还将带来更多的增强功能。
基于这一目标,Angular
团队在最近的版本(从 v13 和 v14 开始)中发布了多项重要更新,
这些更新成为了关键突破,使 Angular
走上了一条近乎革命性的变革之路。
在本书付梓之时,Angular
v19 将作为最新版本发布,它集成了一系列用于解决各种问题的现代工具。
我们将深入探讨近期版本(v12-v18)中的所有功能,无论其大小,并通过示例、实践指南和练习进行详细讲解。
1.1 学习预期
在讨论 Angular
的新特性之前,让我们先明确本书对哪些开发者有用、理解这些概念所需的最低技能和知识水平,以及本书的结构安排。
首先,我们来确定学习内容。
1.1.1 谁将从本书中受益?
鉴于 Angular
的近期变化,以下几类开发者会发现本书非常有用,他们需要深入理解这些新特性:
Angular
新用户:开发者可能来自其他框架,或者刚刚开始使用Angular
,希望更详细地了解其最新特性(前提是具备一定的基础知识)。- 经验丰富的
Angular
开发者:即使是最资深的Angular
开发者也能从本书中获益。 本书全面概述了Angular
v12-v18 中引入的所有新特性和变化。 - 使用旧版本
Angular
的开发者:本书可以帮助那些仍在使用旧版本Angular
的开发者理解升级到最新版本的好处, 以及如何快速、顺利地完成升级。除了介绍近期版本中的新特性和变化,我们还将讨论其他相关主题,如反向移植的更改。
1.1.2 学习前需要了解什么?
要充分吸收本书的信息,需要具备一定的知识。
需要明确的是,本书不是一本从头开始讲解所有内容的 Angular
教程,
而是为已经熟悉 Angular
,并希望深入了解现代方法和工具的开发者编写的新特性指南。
如果你对某些概念不太熟悉,可以参考 Adam Freeman 所著的《 Pro Angular 16 》
(Manning,2024;https://www.manning.com/books/pro-angular-16),
这本书采用教程式的方法,对一些基本概念有详细的解释。
你可以将《 Pro Angular 16 》作为本书的参考资料,有时,我会引用其中的内容来帮助解释一些概念。
本书将(尽管简要地)解释一些叙述过程中必要的更高级概念。
除了这些解释,本书假定读者具备 Angular
、TypeScript
和 HTML
的基础到中级知识。
表 1.1 详细列出了所需的最低知识要求。
技术 | 专业水平 | 详细内容 |
---|---|---|
TypeScript | 基础 | 了解 TypeScript 是什么;知道如何声明变量、函数和对象的类型;了解泛型类型。如果需要,本书会在必要时简要解释更复杂的内容。 |
Angular | 中级 | 由于本书主要关注 Angular ,仅具备基础知识有时是不够的。本书假定读者熟悉 Angular 应用程序的构建块(组件、指令等),并了解 Angular 的内置包,如 Http、路由等。必要时,会解释一些次要的高级概念,或者提供对《Pro Angular 16》的引用。 |
RxJS | 基础 | 只需要了解 RxJS 的最基本知识(主要是可观察对象、操作符和订阅)。 |
HTML | 非常基础 | 具备最基本的 HTML 标签和属性知识,足以理解本书的内容。 |
CSS | 非常基础 | 了解 CSS 选择器即可。本书不侧重于样式,组件示例通常不会包含 CSS 代码。 |
1.1.3 本书的结构
如前所述,许多不同类型、承担各种任务的开发者都会发现本书很有用。
为了最大限度地满足所有读者的需求,本书在解释这些新特性时遵循特定的模式。
首先,我们会提出一个问题,让有早期版本 Angular
开发经验的开发者联想到框架在早期版本中提供的解决方案(或者指出当时不存在此类解决方案)。
然后,我们将讨论在全新的现代 Angular
应用程序中,可用于解决该特性特定问题的新工具。
最后,我将说明开发者如何能够顺利迁移他们现有的 Angular
应用程序以使用新特性。
所有实际示例都构建在一个全新的 Angular
应用程序中。我们将使用最新的 Angular
版本和最新的 Angular
特性从头创建这个应用程序。
该应用程序将执行实际的、现实生活中的功能,以使示例的实用性最大化。我们将在本章后面设置这个应用程序。
既然我们已经设定了先决条件,那么我们可以通过了解在这些革命性变革之前 Angular
的样子,以及这些变革试图解决什么问题,来确定我们的起点。
1.2 Angular
的发展历程
在深入探讨现代 Angular
的新特性之前,让我们简要回顾一下 Angular
的发展历程,了解它是如何演变的,以及之前版本的应用存在哪些问题。
这些背景知识将帮助我们更好地理解为什么会出现这些新特性,以及它们如何解决过去的痛点。
1.2.1 Angular
的核心特性
接下来,我们将讨论 Angular
框架的一些(而非全部)重要部分。我主要关注那些通过最新版本正在发生转变的特性。
面向对象编程
面向对象编程(OOP)长期以来被视为大型企业项目的标志,在 Java 和 C# 等语言的推动下广受欢迎。
它曾是管理复杂应用程序的常用方法。Angular
本身与 OOP 有着相当复杂且紧密交织的关系。
大多数 Angular
构建块,比如组件、管道、指令、守卫以及更多其他构建块,一直以来都是用 OOP 编写的。
所有构建块都由一个类来表示,其内部的数据存储为属性,行为则通过方法来描述。
在接下来的章节中,尤其是第 3 章和第 10 章,我们会看到,其中一些构建块并非必须是类;
实际上,将其中某些构建块用类来表示可能会让开发者感到困惑,特别是对于那些来自其他流行前端框架(如以函数为主导的 React
)的开发者。
我们将看到这种现状即将如何改变。
依赖注入
依赖注入(DI)一直是 Angular
框架中极为重要且颇具吸引力的部分,很难找到在 Angular
项目中未曾使用过它的开发者。
直到最近,DI 还完全与类和面向对象编程(OOP)紧密耦合:
要使用 DI,你需要有一个带有 @Injectable
装饰器的类;
除非将服务实例、配置或 DI 树中的任何其他内容显式作为参数传递,否则你无法在函数中使用它们。
当然,这种限制在一定程度上牺牲了可组合性。随着新的 inject
函数的引入,这一限制已不复存在,开启了可组合性和可复用性的新时代。
基于模块的架构
在 v14 版本之前,所有的 Angular
应用程序都是围绕 NgModule
(一个 Angular
特有的概念)构建的。
NgModule
包含一个类,该类封装了所有其他构建块,并使它们协同工作。
NgModule
被用于构建应用程序架构、共享功能等等。
然而,许多 Angular
开发者在使用 NgModule
时遇到了各种各样的问题。
现在,Angular
允许开发者在不使用 NgModule
的情况下构建应用程序,这是一种被称为 “独立” 的新实践。
RxJS
RxJS
是 JavaScript
的响应式扩展库,它很可能在 Angular
应用程序的不同部分之间共享状态方面发挥着重要作用。
例如,身份验证和授权事件(访问被授予 / 撤销)至少是通过它来传播的。
然而,许多应用程序要么通过带有 Subject
的服务构建一些相对简单的状态管理机制,
要么使用像 NgRx
这样现有的状态管理解决方案,而 NgRx
也依赖于 RxJS
。
新特性极大地提高了 Angular
应用程序与 RxJS
之间的互操作性。
变更检测
变更检测是 Angular
将组件数据中的变化传播到用户界面的机制,它是一个相当复杂且在某种程度上并非最优的算法。
该机制依赖于一个第三方库(zone.js
),开发者们急切地希望找到无需这种额外开销来构建应用程序的解决方案。
在后续章节中,我们将学习如何实现这一点,并深入探究变更检测机制。
1.2.2 什么是 Angular 应用程序?
自 Angular
首次发布以来,出现了多种构建基于 Angular
的应用程序的方式。
诸如 Nx
这样的单仓库工具、静态站点生成器,以及各类不同的构建工具在社区中颇为流行且不断发展。
不过,我们将研究最具 “典型性” 的 Angular
项目,即那些纯粹基于 Angular CLI
构建,采用最常见(不论好坏)方式的项目。
换句话说,我们关注的是在实际场景中更有可能遇到的应用程序类型。
让我们设想一个用于讨论的 Angular
应用程序。我们对这个应用程序做出一系列假设:
- 它使用
Angular
v12 版本,该版本不包含后续较新版本中的现代特性。 - 它未使用专门的状态管理库,比如
NgRx
或Akita
。与许多Angular
项目一样,它通过服务和Subject
逐步实现状态管理。 - 它采用模块化架构,而非特定的模式,例如基于库的架构或微前端架构。
- 它使用了第三方 UI 库(像
Angular Material
等)。具体使用哪个库并不重要,我们只是假设存在这样的依赖。
尽管这些假设可能看起来有所限制,但它们描述了很大一部分现有的 Angular
应用程序。
针对这个应用程序,我们提出一些重要问题:
- 它的主要组成部分是什么?
- 这些部分如何交互,它们之间的联系纽带是什么?
- 在架构层面,开发者面临的最重要且最常见的挑战是什么?
- 在代码层面存在哪些挑战?
- 让新成员加入现有项目的难度如何?
带着这些问题,让我们简要探讨一下实际应用程序的业务情况和独特之处。 在我们的场景设定中,我们将描述一个人力资源管理系统(HRMS)应用程序,它涵盖了人力资源部门的整个工作流程。 它有几个关键部分:
- 员工:包含公司所有员工的数据以及他们的个人资料,人力资源人员和员工本人都可以访问。
- 招聘:有关招聘流程、面试和录用的数据,人力资源人员以及某些员工(通常是进行技术面试的人员)可以访问。
- 休假:员工可以在此申请休假,经理可以进行审批。所有人都可以访问该功能。
- 工作:包含公司正在进行的项目的数据、员工的汇报关系、反馈提交等内容。 访问具有粒度限制,有些员工可以看到所有页面,有些则只能看到部分页面内容。
- 集成:与第三方应用程序(如电子邮件或日历)进行通信。 例如,进行技术面试的人员可能会收到电子邮件邀请,事件会添加到他们的日历中,并且他们可以在 “工作” 部分的个人资料中看到集成的个性化日历。 这可能意味着要与 Outlook、Google Calendar 等第三方服务进行交互。
图 1.1 展示了该应用程序的可视化图表。从表面上看,这个应用程序有多个相互关联的功能。
这意味着该应用程序很可能会使用 RxJS
,可能存在性能问题(部分与变更检测机制相关),
拥有多个服务和可复用的组件/指令,需要仔细维护和构建,并且会有多个 NgModule
。
很明显,我们前面提到的所有功能都会受到 Angular
最新变化的显著影响。
为了研究这个应用程序,我们将做两件不同的事情。
首先,我们将从零开始构建这个应用程序。
其次,正如之前所承诺的,在每一章中,我们将研究这个应用程序已经存在于较旧版本 Angular
上的场景,以便我们了解如何迁移它以使用最新特性。
现在,让我们通过进行基本设置来开始构建这个应用程序,这样我们就可以在后续章节中在此基础上进行拓展。
1.3 让我们启动一个现代 Angular
应用程序
使用 Angular
创建新项目有多种方法,包括使用不同的自定义和第三方构建工具、打包器等。
然而,这些工具虽然无疑是有用的,但不在本书的讨论范围之内。请注意,在整本书中,我们将只使用官方的 Angular CLI
。
如果你在机器上没有安装 Angular CLI
,
可以参考官方 Angular 文档页面(https://mng.bz/PNEY)进行正确安装;如果你安装的是旧版本的
Angular CLI
,
请至少安装 v16.0.0
;如果已经完成了这些步骤,那么可以继续阅读本章,我们将探索现代 Angular
应用程序是什么样的。
1.3.1 使用 Angular CLI
Angular CLI
有许多不同的命令和自定义原理图(schematics
)。
在本书中,我们将逐步接触到不同的(有些是相当新的)脚本,这些脚本允许我们使用特定设置进行构建、生成环境文件、迁移现有代码等。
现在,我们将重点关注可能是最广为人知的命令 ng new
,它用于创建新项目。
ng new
有几个用于自定义新项目的选项,Angular
文档的相关部分对它们进行了详细解释。
在这里,我们将重点关注其中六个选项,如下表 1.2 所示。
参数 | 描述 | 默认值 |
---|---|---|
–strict | 在模板和 TypeScript 文件中启用严格类型检查 |
true |
–inline-template | 默认将组件模板设置为内联。随着独立组件的兴起,这种方法变得非常流行。在本书中,为了确保最大可读性,我们将展示带有内联模板的完整组件,但这并不是一种绝对的好或坏的做法,取决于开发者的偏好。 | false |
–minimal | 创建一个没有任何测试相关文件的项目。这对于学习很有用,但我们不会使用它,因为本书也涵盖了单元测试;如果你想查看简化的项目树,可以自由使用此选项。 | false |
–package-manager | 允许我们选择在项目中使用哪个包管理器(如果我们因为某种原因不喜欢 npm )。Angular CLI 命令(如 ng add 或 ng update )将在内部使用此选项来安装和更新依赖项。在本书中,我们将使用默认值,但你可以自由探索其他选项。 |
npm |
–standalone | 这个选项对我们来说是最重要的,因为它默认创建一个不使用 NgModules 的应用程序,就像现代 Angular 应用程序一样。我们将直接使用这个选项。 |
true |
-defaults | 跳过提示问题并使用默认值,而无需询问。例如,它将生成 Angular 路由,使用 CSS 作为样式等。 |
false |
1.3.2 创建新项目
现在,我们已经熟悉了几个命令选项,那就让我们继续并最终创建一个新应用程序。
如果你使用的是 Angular v16
,就必须手动指定你想要一个基于独立组件的应用程序。
在 v17
及更高版本中,可以跳过此参数。
导航到你选定的文件夹/目录,然后运行以下命令:
1
$ ng new hrms --defaults --standalone --routing
注意
根据你的
CLI
版本(v16
或更低版本),你可能会看到一条警告, 提示 “独立应用程序结构是新的,许多现有的ng add
和ng update
与社区库的集成尚未支持它”。 这种情况大多在使用第三方库时出现,与我们的应用程序关系不大,所以你可以放心忽略它。
安装完成后,我们应该就能看到我们的新项目了。 用你选择的编辑器打开它,首先来探索一下文件夹结构。很可能,这个应用程序看起来如下所示:
1
2
3
4
5
6
7
└── hrms/
├── src/
├── angular.json
├── package.json
├── tsconfig.app.json
├── tsconfig.json
└── tsconfig.spec.json
请注意,这里可能还有其他未提及的文件,比如与 Git
相关的文件、编辑器自动生成的特定文件、README.md
文件等等。
让我们留意与旧版本相比的三个重要变化:
- 没有
environments
文件夹:环境文件用于存储应用程序的配置数据,例如 API URL 或可能因环境而异的第三方 API 配置。 从Angular v15
开始,环境文件不再默认生成,可以通过单独的命令添加。我们将在第 9 章中更多地讨论环境以及构建/部署方面的内容。 - 没有显式的
polyfills.ts
文件:polyfills
用于支持旧版浏览器,比如 IE11 或更早版本。 以前,Angular
项目从一开始就默认有这个文件,但现在不再自动生成。如果需要支持旧版浏览器,也可以手动添加。 angular.json
文件更短:如果我们打开angular.json
文件,会注意到它比旧项目中的要短得多。
1.3.3 发生了哪些变化?
现在,让我们打开最有意思的文件夹 src
,看看里面有什么:
1
2
3
4
5
6
7
8
9
10
11
12
13
├── hrms/
│ └── src/
│ ├── app/
│ │ ├── app.component.css
│ │ ├── app.component.xhtml
│ │ ├── app.component.spec.ts
│ │ ├── app.component.ts
│ │ ├── app.config.ts
│ │ └── app.routes.ts
│ ├── assets/
│ ├── index.xhtml
│ ├── main.ts
│ └── styles.css
在这里,我们可以看到与我们所习惯的情况相比,另外有三处不同:
- 没有
app.module.ts
文件 —— 该应用程序是完全独立的,其架构不使用模块。 - 有
app.routes.ts
文件,而不是app.routing.module.ts
—— 这同样是因为我们选择了独立模式。 - 有
app.config.ts
文件 —— 这个文件将包含我们应用程序的全局配置,比如提供者、路由初始化等内容。
现在,让我们开始探索这些文件的内容本身。
app.component.xhtml
包含一个预定义的欢迎页面,
app.component.spec.ts
包含一些样板单元测试,
app.component.css
是空的,所以我们将跳过它们。
现在让我们查看 app.component.ts
的内容。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterOutlet } from '@angular/router';
@Component({
selector: 'app-root',
standalone: true,
imports: [CommonModule, RouterOutlet],
templateUrl: './app.component.xhtml',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'hrms';
}
这段代码看起来像是一个相当常见的 Angular 组件,但它有两个重要的区别:
standalone: true
将此组件标记为独立组件,不属于任何NgModule
。imports
数组用于导入依赖项,比如其他模块以及独立组件/指令/管道, 因为这个组件是独立的,不依赖NgModule
来查找其依赖项,而是直接导入它们。 请注意,在 v16 版本中,它导入CommonModule
以便能够使用内置指令和管道,但现在这些东西也都是独立的,可以直接导入。 也就是说,我们可以在imports
数组中写入NgIf
,只导入它本身,而无需导入整个CommonModule
。 在 v17 及更高版本中,CommonModule
不会默认被导入。
这就是一个典型的独立组件的样子,但当然,它还有更多内容,我们将在下一章讨论。现在让我们看看 app.routes.ts
文件。
1
2
import { Routes } from '@angular/router';
export const routes: Routes = [];
我们可以看到它也更简单,因为它没有使用 RouterModule
来注册路由。
相反,这些路由只是在这里定义,并在 app.config.ts
文件中注册,我们现在就来看看这个文件。
1
2
3
4
5
6
7
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [provideRouter(routes)]
};
同样,这里有两个重要的点需要注意:
ApplicationConfig
接口有一个属性providers
,它用于提供依赖注入(DI)令牌, 就像我们之前在NgModule
中使用providers
属性时所做的那样。- 路由是通过一个名为
provideRouter
的特殊新函数来注册的,该函数也接受我们的路由定义数组, 而不是使用RouterModule.forRoot(routes)
。
现在看来我们已经查看了所有文件,因为,正如之前提到的,在独立设置中我们没有 app.module.ts
文件。
那么,如果我们没有那个文件,我们的应用程序是如何初始化和启动的呢?嗯,这个逻辑完全转移到了 main.ts
文件中。
1
2
3
4
5
6
import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, appConfig)
.catch((err) => console.error(err));
正如我们所见,main.ts
现在使用一个特殊的 bootstrapApplication
函数,
而不是之前的 platformBrowserDynamic().bootstrapModule(AppModule)
,并且直接启动 AppComponent
而不是 AppModule
。
在现代 Angular
应用程序中,我们不需要 NgModule
,这个新函数可以使用一个根组件和应用程序配置直接创建我们的应用程序。
到目前为止,我们已经看到了与独立组件以及依赖注入(DI)方式相关的变化。 在继续探讨其他变化之前,我们将在接下来的两章中全面介绍这些主题。 现在,让我们简要讨论一下所有这些变化,以便明确我们的学习路线。
1.4 Angular 有哪些新特性?
在我们开始更深入地亲身体验 Angular
中最新、最热门的特性之前,
让我们先来讨论一下 Angular
团队是如何发现问题并决定解决方案的,这样我们就能理解所有这些新特性背后的逻辑。
1.4.1 Angular 是如何发展演变的?
Angular
是一个开源框架,拥有充满活力的贡献者和用户社区。
正因如此,一般用户的某些期望常常最终会在框架代码中实现为实际的功能和改进。
让我们来看看这背后的过程;了解某些决策是如何做出的,并且有可能亲自参与这个过程,这会很有帮助。
对于某个想法要成为 Angular
框架中的一个具体功能,它必须经过严格的步骤,如图 1.2 所示。
正如我们所见,这是一个相当漫长的过程,有着许多里程碑;同样值得注意的是,还有一些我们并不知晓的内部流程,大多与维护相关。
但公开的流程在一定程度上让 Angular
变得 “民主”;
虽然核心团队在变更和 API 结构方面拥有最终决定权,但来自更广泛社区的贡献者常常会提出变更建议,
或者直接(通过拉取请求)编写代码,我们将在本书中讨论的许多特性就是这样诞生的。
让我们更详细地讨论这个过程中的第一步。
1.4.2 Angular 如何识别问题?
正如之前所描述的(当然,常识也告诉我们),在框架中添加新特性的第一步是识别开发者常见且普遍的需求,
或者痛点(也许是我们之前讨论过的那些痛点之一)。
这个过程使用了几种方法 —— 具体来说,包括核心团队的内部研究、对 Web 标准变化的探索、来自更广泛社区在 GitHub 上提交的问题,
以及社区成员直接贡献的拉取请求。
最后,Angular
开发者调查(https://mng.bz/w5q2)是一项每年发送给所有感兴趣的 Angular
开发者的调查,
他们可以在调查中回答常见问题,写下他们认为需要改变的事情等等。
在收集了这些信息之后,通常会展开讨论,贡献者和用户可以就他们特别感兴趣的问题进行投票;
这是该框架中民主决策的又一个体现,也再次证明了 Angular
是多么注重反馈驱动,并且常常倾听开发者社区的意见,以适应新的挑战。
然而,非常重要的是要知道,尽管民主程序在 Angular
中推动了许多创新,但核心团队仍然拥有最终决定权。
有时,一个需求很高的特性可能不会被实现,主要是因为它与团队的一些核心价值观相冲突
(例如,向后兼容性、与 Web 标准不一致,以及我们已经讨论过的其他因素)。
牢记这一切,让我们来看看 Angular
是如何开辟未来之路的。
1.4.3 当前目标
关于 Angular
如何选择发展方向,一个重要的信息来源是 Angular
官方路线图(https://angular.io/guide/roadmap),
这是一份详细的列表,列出了 Angular
在未来版本中将要实现的功能。
在那里我们可以看到大量的主题,但我们现在不会对其中任何一个进行探讨,因为它们中的大多数目前还只是推测性的。
然而,熟悉路线图可以让我们了解未来可能会有什么。
相反,我们将探讨一些当前的核心目标 —— 这些是重大的短期变化,它们要么改善了开发者体验,要么为更彻底的变革奠定了基础。
这些变化将反映在 Angular
的较新版本中已经存在的一些特性,并非是推测性的。
我们将结合前面提到的 HRMS 企业应用程序来审视这些目标,以便了解这样一个应用程序的哪些部分可能会得到改进。
易于采用
关于 Angular
的另一个常见(且至少部分正确)的刻板印象是,它很难学习或切换使用。
这主要与 Angular
开箱即用提供的大量功能有关,包括非常独特的模板语法,以及框架引入的其他纯 Angular
相关概念,如指令、管道等等。
核心团队的另一个重要目标是,通过改进文档或直接简化核心概念,使该框架对新手更加友好。
增强的可组合性/可复用性
一些 Angular
的构建模块可能难以复用或重新利用。
例如,我们可能有多个路由守卫或拦截器,它们在不同的上下文中基本上做着相同的事情(比如检查某些角色或权限,正如前面提到的),
并且难以进行通用化,这可能会导致复制粘贴相关的问题,以及产生本可避免的错误。
这也严重影响了重构代码库的能力:更改一个路由守卫可能意味着要更改所有的路由守卫。
我们可能还希望将这种可复用性应用于处理业务逻辑的指令,以使我们的模板更加动态。
减少样板代码
总体而言,与其他前端框架相比,Angular
被认为样板代码稍多。
因此,核心团队的目标之一(尽管不是最重要的目标)是减少启动并运行 Angular
所需的代码量。
在我们的应用程序中,可能有大量样板代码来实现一些核心功能,
比如定义管道和可复用组件、提供服务和环境配置、共享状态、创建路由守卫以及其他处理程序。
增强的类型安全性
由于 Angular
已经构建在 TypeScript
之上,我们从中获得了很多类型安全性。
然而,当我们深入研究时,会发现框架本身的许多部分包含大量的 “any
” 类型,这会并且将会使我们的应用程序在严格类型检查方面变得不那么安全。
最突出的是响应式表单,在处理大量用户生成数据的企业应用程序中,响应式表单可能非常重要且被广泛使用。
改进的响应式
在图 1.1 中,我们看到了许多相互关联的组件和应用程序部分,并且很有可能通过 Angular
服务使用 RxJS
将这些元素连接起来。
这些连接带来了复杂性,并且出现故障和错误的可能性更高。简化这个过程对大型应用程序将有很大益处,并且会使它们对新手更加友好。
1.4.4 新特性
从 Angular
v13 开始,引入了许多有趣且有时是重大的改进,所有这些改进都针对我们之前讨论的主题。
由于本书完全致力于介绍 Angular
的现代状态,我们在本章不会对此深入探讨,但我们将熟悉这些新增功能的总体概述。
独立构建块
在启动我们的人力资源管理系统(HRMS)应用程序时,我们已经遇到了一个独立组件。
从 v14 版本开始,不再需要 NgModule
:Angular
的构建块现在可以是独立的,这意味着它们在应用程序中使用时不需要关联的 NgModule
。
在 v15 版本中,这一新功能被标记为 “稳定”,并且已经在许多可投入生产的应用程序中广泛使用。
我们将在下一章讨论这种新方法及其所有优点(以及一些新的陷阱)。
注入函数
在 v14 版本之前,仅能在标记有 Angular
装饰器
(@Component
、@Pipe
、@Directive
,当然还有 @Injectable
)之一的类中注入依赖项。
若要在函数(而非类)中直接通过参数传递来使用某个依赖注入(DI)值的实例,这是不可能的。
有了这个新函数,我们能够克服这一限制,并构建一个可组合、可复用的函数,该函数能以最简单的方式在组件之间轻松共享。
这个小小的变化(本质上是 Angular
将早已存在的 inject
函数公开了出来)在 Angular
社区中引起了巨大反响,
它改变了开发方式,实现了前所未有的可组合性,甚至导致官方弃用了一些此前广泛使用的工具。
我们将在第 3 章深入探讨新的依赖注入模式。
类型安全的响应式表单
长期以来,响应式表单一直是类型相关错误的根源,开发人员不得不采用各种(有时相当麻烦的)解决方案来克服这些限制。 同样,从 v14 版本开始,引入了新的类型化响应式表单,现在已被标记为 “稳定”, 它们迅速取代了之前的表单(之前的表单已重命名为非类型化响应式表单,以简化迁移过程)。 我们将在第 4 章进一步讨论它们。
指令组合 API
在组件/指令元数据对象中添加了一个新的 hostDirectives
属性,它允许自动添加其他指令,本质上使我们能够从其他指令构建指令。
这对于可组合性来说是一个巨大的进步,此前可组合性是通过其他(并不总是非常合适的)解决方案实现的,
比如面向对象编程(OOP)继承或纯依赖注入(DI)。
我们将在第 4 章看到它如何简化指令 / 组件代码以及模板。
与 RxJS 更好的兼容性
Angular
中添加了一个全新的包 rxjs-interop
,它将帮助开发人员将 RxJS
代码无缝集成到 Angular
应用程序中。
这个新的响应式原语允许在信号和可观察对象之间进行切换,反之亦然,并且该包有一种更简单的内置方式来取消对数据流的订阅。
我们将在第 5 章探索这个新包。
信号
信号可能是 Angular
有史以来最具成效的新增功能。它是一种新的响应式原语,旨在解决我们在使用 RxJS
时遇到的常见问题。
信号转变并显著改进了变更检测机制,将其提升到一个新的、更精细的级别,而非目前的自上而下的检查系统。
我们将在第 6 章和第 7 章详细讨论信号。
新模板语法
从 v17 版本开始,一种新的模板语法可用,预计将取代 ngIf
、ngSwitch
和 ngFor
指令。
这种语法使模板更易读,并且有利于编译器优化。在 v18 版本中,这种语法已经稳定。我们将在第 10 章讨论这些新指令。
模板部分的延迟加载
新模板语法的另一个特性是允许基于条件或事件对模板的一部分进行延迟加载。 这有助于我们构建性能更佳的应用程序,同时还能减小最终的打包大小。我们将在第 10 章广泛讨论这种方法。
单元测试的新工具
新单元测试框架的加入、对新 API(如前面提到的 inject
函数)的支持,以及人工智能工具的出现,
都显著影响了我们编写单元测试的方式,带来了更快的编写体验,并且减少了模拟数据所花费的时间。
在第 8 章,我们将深入了解单元测试如何推动现代 Angular
开发。
服务器端渲染水合(Server-Side Rendering Hydration)
长期以来,服务器端一直是 Angular 的薄弱环节之一,仅支持最基本的页面全量渲染, 但最近的发展增添了人们期待已久的功能,即全量水合(full hydration)。 全量水合极大地提升了服务器端渲染(SSR)应用程序的性能,允许复用现有的应用程序状态和文档对象模型(DOM)。 在第 9 章中,我们将审视这一特性,并熟悉开发体验的改进以及新的实验性打包系统。
性能方面的各种精细化改进
出现了不同的小工具,它们能够改善页面及其不同部分的加载情况,比如图像的加载,这些工具已经稳定可用。 我们会在不同章节中遇到这些新增功能,主要是在第 3 章。
开发者体验改进
Angular 中已经有了更好的错误消息、调试功能、堆栈跟踪等,并且还有更多改进正在进行中。我们会在各个章节中探讨这些特性。
总结思考
正如我们所见,这些变化是对该框架的重大补充,完全可以被视为具有 “革命性”。
有时开发者将其称为 “Angular 3”,这是把 AngularJS
当作 “Angular 1”,v12 之前的框架当作 “Angular 2”,
而现在这些新变化被标记为 “Angular 3”。
然而,这些名称具有误导性,与实际情况不符。
Angular
团队并未发表任何声明称这是一个全新的框架。
尽管这些变化有些重大,但它们都是渐进式的,易于采用,并且在很大程度上是向后兼容的。
从 AngularJS
到 Angular
是对框架的全新重写,引入了新的概念和方法。
这些更新绝不是对整个框架的全面彻底改造 (尽管可以公平地说,比如说,未来的 Angular v18 和 Angular v12 之间的差异会非常显著)。 所以,我们不会将其称为 “Angular 3” 或其他人可能想出的任何其他标签; 相反,我们将其视为 “Modern Angular”,即该框架的当前状态。
1.4.5 未来会怎样?
在继续之前,让我们简要提及读者可能会提出的另外两个相关问题,即 “我们接下来可能期待什么?” 以及 “哪些内容肯定会保持不变?”
让我们从未来的前景开始(见表 1.3)。 请注意,这些不是基于个人偏好的猜测,而是框架中已经可供开发者预览的一些实验性内容,或者是已经公开的征求意见稿(RFC,Request for Comments)。 我们不会在本书中详细讨论它们,但它们值得了解,并且理解现有变化背后的动机也很有帮助。
变更 | 描述 | 版本 |
---|---|---|
无区域应用程序 | 自 Angular 诞生以来,Zone.js 一直是其必备部分。然而,它会增加捆绑包大小,并且在变更检测方面效率不高。随着新的响应式原语的出现,Angular 团队正在探索实现完全无区域应用程序的可能性。目前,通过一些调整作为实验性 API,这是可行的,但默认情况下并非如此。 | v18 |
替代组件编写格式 | 如前所述,以前一些只能用类实现的构建块(如路由守卫和解析器),现在可以(并且更推荐)以函数的形式编写。Angular 团队有可能也会允许功能性的管道、指令,甚至组件,或者可能是一种完全不同的编写格式。 | 未知 |
部分水合和可恢复性 | 我们将在第 9 章讨论全应用程序水合,因为全水合已经是框架的稳定部分;然而,核心团队也将探索服务器端渲染(SSR)中页面的部分水化和应用程序的可恢复性。 | 可能在 v19 |
模板中的 let 关键字 | 这个新关键字将允许开发者在模板中声明变量,简化了向模板中添加复杂逻辑的过程。这些变量仅在模板内部可用。 | 可能在 v18.1 |
第二个问题同样重要。以下是一些我们可以认为非常稳定、不会有重大变化的内容:
- 依赖注入(DI)机制:虽然未来会有一些对 DI 的小改进(比如能够在应用程序的根级别提供一个
InjectionToken
), 但 DI 框架本身已经经受住了时间的考验,仍然是Angular
开发者最喜欢的功能之一。 - 以前的组件编写格式:虽然可能会有函数式组件,但基于类的组件至少在可预见的未来不会消失。
RxJS
支持:虽然Angular
引入了一种新的响应式原语,但我们将在本书中看到与RxJS
的集成会变得更加顺畅, 并且即使一些开发者可能会选择少用RxJS
而倾向于信号,与RxJS
良好协作的核心原则仍将保留。- 路由:在引入函数式守卫和其他一些小改进之后,
Angular
路由没有计划进行重大更改。 - HTTP 客户端、动画和其他可选工具:这些工具是框架中肯定经受住了时间考验的部分。
Angular
的未来看起来非常光明,但为了理解并更好地为其做好准备,我们需要审视它的当前状态。
1.4.6 学习过程
如我们所见,本书的每一章都将围绕一个(或多个)新概念展开,并探索它们在实际中的应用。
在这种情况下,每一章将大致按照我们之前设定的模式来架构:探索一个主题以及变化背后的理论,通过代码示例应用它,然后让读者进行编码实践。
所有的解决方案都将包含在 GitHub
存储库中,但我建议手动实现这些代码,以提高读者的技能熟练程度。
我希望这个过程既有趣又富有启发性,大量的实际编码将有助于巩固您对 Angular
的知识。
所以,从这里开始,我们将深入探索每一个新特性,同时构建我们的现代 Angular
应用程序。
小结
Angular
历史丰富,且不断发展。- 社区已经发现了该框架的一些突出问题。
- 核心团队不断提出并实施新的方法来解决这些问题。
- 大量新的概念已经出现,如新的依赖注入、独立构建块、信号、类型安全的表单、函数式构建块等等。
- 现在新项目使用独立的方法创建。
> 返回扉页