如果只能用一个词来概括 Angular 的优点,那我会选「工程化」;如果要换个文艺点的词,我会说 「大巧若工,大道至简」。这个诞生于一群 Google 工程师之手的框架,从一开始就打上了鲜明的「工程化」烙印。
什么是工程化开发呢?它是和手工作坊式的开发相对而言的。主要的特征是需要多人、多时段的分工与配合,而不是一个超级程序员写完90%的代码,其他人只负责打下手。

但大中型系统面对的挑战不是靠超级程序员就能解决的。如果一个系统的复杂性逐渐累积,迟早会让它的维护成本越来越高,变成鸡肋一般的存在。要解决复杂性的问题,首要办法就是从架构层面上隔离系统的各个部分,让它们彼此之间相互独立,互不干扰,也就是说,分而治之,各个击破。然而这并不容易。
多人开发最大的挑战就是如何分工协作。#
在现代 Angular 中,支持分工协作的主要基础设施,是清晰的代码组织结构与强大的依赖注入(DI)体系。
在过去,我们严重依赖 NgModule 来划分应用的「硬边界」。而在以独立组件(Standalone Components)为主流的今天,这种边界感通过一种更灵活、更自然的方式得以实现。想象一个 Monorepo 工作区,我们通常会建立应用和库两类目录:
-
库目录:这里存放的是可复用的「零件」,比如
ui-kit(共享组件库)、data-access(数据访问服务)、utils(工具函数)等。每个库都由一个或多个专注的团队负责。 -
应用目录:这里存放的是最终的产品,比如
main-app(主应用)、admin-app(后台应用)。
这种组织方式,天然地形成了团队间的「硬边界」。ui-kit 团队的核心使命就是打磨出高质量、可复用的 UI 组件。他们无需关心 main-app 的业务逻辑。
而将这些独立的「零件」和「产品」无缝粘合起来的「万能胶」,就是 Angular 的依赖注入体系。
-
main-app中的一个组件,可以通过inject(DataService)来使用来自data-access库的服务,而无需关心这个服务内部是如何实现 HTTP 请求的。 -
如果将来我们需要更换数据来源,
data-access团队可以无声无息地替换掉服务的内部实现,而main-app的代码一行都不用改。
这种模式,将「代码的物理组织」和「逻辑的依赖关系」完美解耦。每个团队都可以专注于自己的「一亩三分地」(自己的项目或库),并通过 DI 这个「标准接口」与其他团队协作,避免了混乱的意外耦合。这才是现代前端工程中,「分而治之」的精髓。
除了意外耦合的问题之外,多人开发的另一个挑战就是契约。#
手工作坊式的开发可能不需要显式的契约,因为超级程序员「心怀宇宙」,了解整个系统的每一处关键点。但人一旦多了,这种方式的弊端就会显露出来,毕竟人类的心灵并不相通,并没有神圣的卡拉连接你我。这个时候,就需要显式地描述契约,并借助工具来保障这些契约没有被误解。
在 Angular 中,用来应对契约问题的主要基础设施是类型与测试。
Angular 借助 TypeScript 来对类型提供支持。通过类型,Angular 可以对接口契约进行一定程度的表达。在开启了严格模式,特别是 strictTemplates 之后,这份「契约」甚至延伸到了模板中,确保你在绑定属性、调用方法时,类型都是安全的。我们都知道,发现错误越早,解决它的代价就越低。
不过,类型只能对契约进行一定程度的表达,但它无法表达「我要一个大于10小于1000的数字」之类的契约,这时候就需要不同层次的测试出手了。从最早的 AngularJS 开始,测试就一直是重中之重。在现代 Angular 中,测试一个独立组件变得前所未有地简单,因为你不再需要为了测试而建立一个复杂的 TestBed 模块,只需在 TestBed.configureTestingModule 的 imports 中加入该组件依赖的少数几个「零件」即可。
多人开发中一个稍微容易但更加繁琐的问题是代码风格。#
一个团队要想形成统一的代码风格,「写得像一个人一样」其实相当麻烦。光靠制订代码规范是没用的,《规范.exe》胜于《规范.txt》。Angular CLI 从创建项目伊始,就为你配置好了 ESLint 和 Prettier,并提供了官方的风格指南。这让团队可以把精力从「代码该怎么写好看」的争论中,解放出来,聚焦于业务逻辑本身。
时间是系统最大的客户,也是系统最大的敌人。#
一个只为打印一次 Hello, world! 而写的程序不需要任何工程化。但那些真正的系统势必会随着时间的推移而不断演化,而演化的过程,就是一场与「熵增」(系统混乱度增加)的持续对抗。
Angular 如何抵抗岁月的侵蚀呢?
首先,Angular 的设计原则是每个部件只关注一件事(关注点分离 SoC)。
-
从宏观上说,通过对应用与库的划分,
ui-kit只需要关心 UI,data-access只需要关心数据。 -
从微观上说,服务拆分出了业务逻辑,组件只负责展现,而指令和管道则负责能力的扩展与数据的转换。这种清晰的职责划分,让需求变更带来的影响被最小化了。
其次,Angular 的版本发布策略在保持稳定和拥抱变化之间做出了较好的平衡。 Angular 遵循语义化版本(semver)规范,并提供了强大的 ng update 命令。这个命令不仅仅是简单地升级版本号,它还会运行一系列「迁移脚本(migration schematics)」,自动地、安全地将你的代码从旧语法更新到新语法。从 NgModule 到独立组件,从旧的控制流指令到新的 @if/@for 语法,这些重大的演进,很多都可以通过 ng update 来平滑过渡。更重要的是,这个命令不止覆盖 Angular 框架的内置功能,Angular 生态中略有规模的第三方库也同样支持这种能力,让你可以无脑升级,安心升级。这是 Angular 工程化能力的极致体现。
最后,Angular 既老且新。#
说它「老」,是因为 Angular 采用了大量有几十年历史的、经历过岁月重重考验的成熟软件工程实践,比如依赖注入、面向接口编程、关注点分离等。难能可贵的是,Angular 从未给它们发明新的名词进行包装,要知道,前端圈最喜欢做的事可能就是发明新名词了。
说它「新」,是因为 Angular 热情拥抱其它上下游技术,并积极追随标准。从最初选择与少年时期的 TypeScript 合作,到与 RxJS 的深度整合,再到近年来,它更是以壮士断腕的决心,引入了 Signals 这一更精细的响应式模型,并大刀阔斧地推行独立组件,朝着更简洁、更高效、甚至无 Zone.js 的未来迈进。
结语#
工程化的本质目的就是支持团队化开发并帮助系统抵抗时间的侵蚀。Angular 展现出的这些工程之美 —— 无论是其稳定的软件工程内核,还是其不断进化的现代化「形态」 —— 都让我对 Angular 的未来充满信心。
周虽旧邦,其命维新!