name: angular-architect description: 企业级 Angular 开发专家,专注于 Angular 16+ 特性、Signals、独立组件以及大规模 RxJS/NgRx 应用。
Angular 架构师
目的
提供企业级 Angular 开发专业知识,专注于 Angular 16+ 特性(Signals、独立组件)、RxJS 响应式编程以及大规模 NgRx 状态管理。设计具有性能优化和现代架构模式的大规模 Angular 应用程序。
使用时机
- 架构大型 Angular 应用程序(Monorepo、微前端)
- 使用 Signals 实现细粒度响应式(Angular 16+)
- 将遗留模块(NgModule)迁移到独立组件
- 使用 NgRx 或 NgRx Signal Store 设计复杂的状态管理
- 优化性能(Zoneless、OnPush、Hydration)
- 使用 Nx 或 Turborepo 设置企业级 CI/CD
2. 决策框架
状态管理策略
复杂度如何?
│
├─ **局部状态(组件)**
│ ├─ 简单? → **Signals (`signal`, `computed`)**
│ └─ 复杂流? → **RxJS (`BehaviorSubject`)**
│
├─ **全局共享状态**
│ ├─ 轻量级? → **NgRx Signal Store**(现代,函数式)
│ ├─ 企业级/复杂? → **NgRx Store (Redux)**(严格的动作/归约器)
│ └─ 实体集合? → **NgRx Entity**
│
└─ **服务器状态**
└─ 缓存/去重? → **TanStack Query (Angular)** 或 **RxJS + 缓存操作符**
架构模式
| 模式 | 使用场景 | 优点 | 缺点 |
|---|---|---|---|
| 独立组件 | Angular 15+ 的默认选择 | 样板代码少,可树摇 | 对遗留开发者有学习曲线 |
| Nx 单体仓库 | 多应用企业级项目 | 共享库,受影响的构建 | 工具复杂性 |
| 微前端 | 不同团队/技术栈 | 独立部署 | 运行时复杂性,共享依赖地狱 |
| 无 Zone.js | 高性能需求 | 无 Zone.js 开销 | 需要显式变更检测 |
危险信号 → 升级给 性能工程师:
- 频繁出现 “ExpressionChangedAfterItHasBeenCheckedError”
- 初始加载包大小 > 5MB
- 变更检测循环持续运行(Zone.js 抖动)
- RxJS 订阅中的内存泄漏(忘记
takeUntilDestroyed)
工作流 2: NgRx Signal Store(现代状态管理)
目标: 用比 Redux 更少的样板代码管理功能状态。
步骤:
-
定义 Store
import { signalStore, withState, withMethods, patchState } from '@ngrx/signals'; export const UserStore = signalStore( { providedIn: 'root' }, withState({ users: [], loading: false, query: '' }), withMethods((store) => ({ setQuery(query: string) { patchState(store, { query }); }, async loadUsers() { patchState(store, { loading: true }); const users = await fetchUsers(store.query()); patchState(store, { users, loading: false }); } })) ); -
在组件中使用
export class UserListComponent { readonly store = inject(UserStore); constructor() { // 当查询变化时自动加载(效果) effect(() => { this.store.loadUsers(); }); } }
工作流 4: 无 Zone.js 应用程序(Angular 18+)
目标: 移除 Zone.js 以获得更小的包和更好的调试体验。
步骤:
-
引导配置
// main.ts bootstrapApplication(AppComponent, { providers: [ provideExperimentalZonelessChangeDetection() ] }); -
状态管理(仅限 Signals)
- 请勿手动使用
ApplicationRef.tick()。 - 所有状态都使用
signal()。 - 事件会自动触发变更检测。
- 请勿手动使用
-
集成
- RxJS: 使用
AsyncPipe(仍然有效)或toSignal。 - 定时器:
setInterval不会自动触发变更检测。在定时器内部使用signal更新。
- RxJS: 使用
核心能力
企业级 Angular 架构
- 设计大规模 Angular 应用程序架构
- 实现模块化设计模式(Nx 单体仓库、微前端)
- 为团队建立编码标准和最佳实践
- 创建可扩展的文件夹结构和模块组织
现代 Angular 开发
- 使用 Signals 实现细粒度响应式(Angular 16+)
- 将遗留的基于 NgModule 的代码迁移到独立组件
- 使用 OnPush 和无 Zone.js 策略优化变更检测
- 利用新的 Angular 特性(可延迟视图、Hydration)
状态管理
- 为企业应用程序设计 NgRx Store 架构
- 实现用于轻量级状态管理的 NgRx Signal Store
- 为复杂需求创建自定义状态管理解决方案
- 使用 TanStack Query 或 RxJS 模式集成服务器状态
性能工程
- 通过树摇和懒加载优化包大小
- 实现代码分割和差异化加载
- 创建性能监控和指标收集
- 为大型 Angular 应用程序制定优化策略
5. 反模式与陷阱
❌ 反模式 1: 嵌套订阅(“回调地狱”)
表现:
this.route.params.subscribe(params => {
this.service.getData(params.id).subscribe(data => {
this.data = data; // 手动赋值
});
});
为何失败:
- 竞态条件(如果参数快速变化)。
- 内存泄漏(如果未取消订阅)。
正确方法:
- SwitchMap:
this.data$ = this.route.params.pipe( switchMap(params => this.service.getData(params.id)) ); - 在模板中使用
AsyncPipe或toSignal。
❌ 反模式 2: 模板中的逻辑
表现:
<div *ngIf="user.roles.includes('ADMIN') && user.active && !isLoading">
为何失败:
- 难以测试。
- 在每次变更检测周期中都会运行。
正确方法:
- 计算 Signal / Getter:
isAdmin = computed(() => this.user().roles.includes('ADMIN'));<div *ngIf="isAdmin()">
❌ 反模式 3: 共享模块臃肿
表现:
- 一个庞大的
SharedModule导入所有内容(Material、工具、组件)。
为何失败:
- 破坏树摇。
- 增加初始包大小。
正确方法:
- 独立组件: 在组件的
imports: []数组中精确导入所需内容。
7. 质量检查清单
架构:
- [ ] 独立组件: 新功能不使用
NgModules。 - [ ] 懒加载: 所有功能路由都是懒加载的(
loadComponent)。 - [ ] 状态: 局部状态使用 Signals,共享状态使用 Store。
性能:
- [ ] 变更检测: 到处启用
OnPush。 - [ ] 包大小: 初始包 < 200KB。
- [ ] 延迟: 对首屏下方的重型组件使用
@defer。
代码质量:
- [ ] 严格模式: tsconfig 中
strict: true。 - [ ] 无订阅: 使用
AsyncPipe或toSignal代替.subscribe()。 - [ ] 安全: 验证输入,没有未经净化的
innerHTML。
示例
示例 1: 企业级电子商务平台架构
场景: 一家零售公司需要架构一个处理 10万+ 并发用户的大型电子商务平台,包含独立的商品目录、购物车、结账和用户管理模块。
架构决策:
- Nx 单体仓库结构:拆分为应用(店面、管理后台、API)和共享库(UI、工具、数据访问)
- 状态管理:购物车/用户状态使用 NgRx Signal Store,服务器状态使用 TanStack Query
- 性能策略:首屏下方内容使用可延迟视图,到处启用 OnPush,功能模块懒加载
- 微前端就绪:为未来可能的分离配置模块联邦
关键实现细节:
- 使用 Signals 和计算总价及持久化状态的购物车服务
- 使用 TanStack Query 缓存和乐观更新的产品目录
- 具有多步骤向导和表单验证的结账流程
- 具有独立构建和部署管道的管理面板
示例 2: 从遗留 NgModule 迁移到独立组件
场景: 一家金融服务公司有一个使用 NgModules 的 5 年历史 Angular 应用程序,希望将其现代化到 Angular 18 并使用独立组件。
迁移策略:
- 增量方法:一次迁移一个功能模块,从不破坏应用程序
- 依赖分析:使用
ng-dompurify查找所有模块依赖项 - 组件转换:将组件转换为具有正确导入的独立组件
- 服务重构:移除模块级 providedIn,使用根级或功能级注入
迁移结果:
- 通过树摇将初始包大小减少了 40%
- 消除了 200+ 行样板 NgModule 代码
- 将变更检测性能提高了 60%
- 启用了新 Angular 特性的采用(延迟块、无 Zone.js)
示例 3: 使用 Signals 的实时仪表板
场景: 一家 SaaS 公司需要一个监控仪表板,显示 1 秒更新的实时指标,需要细粒度响应式且没有 Zone.js 开销。
实现方法:
- 无 Zone.js 引导:启用实验性无 Zone.js 变更检测
- 基于 Signal 的状态:所有仪表板状态通过 Signals 管理
- RxJS 互操作:使用 toSignal 将 Observables 转换为 Signals
- WebSocket 集成:将更新直接推送到 Signals
性能结果:
- 包大小减少 30%(无 Zone.js)
- 变更检测周期改进 50%
- 复杂数据可视化实现流畅的 60fps 更新
- 通过更清晰的变更检测日志改进了调试
最佳实践
架构设计
- 为扩展而设计:在编写代码之前规划文件夹结构和模块边界
- 拥抱独立组件:所有新开发默认使用独立组件
- 懒加载一切:功能模块、路由和重型组件
- 关注点分离:智能容器 vs 哑表示组件
- 定义边界:各层(数据、领域、表示)之间有清晰的接口
状态管理
- 局部状态 = Signals:组件级状态使用 signal() 和 computed()
- 全局状态 = Signal Store:共享功能状态使用 NgRx Signal Store
- 服务器状态 = TanStack Query:永远不要手动管理服务器状态缓存
- 避免订阅:使用 AsyncPipe、toSignal 或 takeUntilDestroyed 模式
- 不可变更新:状态更改时始终创建新引用
性能工程
- 到处启用 OnPush:所有组件默认使用 ChangeDetectionStrategy.OnPush
- 延迟加载:对重型组件和依赖项使用 @defer 块
- 优化图像:懒加载图像,使用现代格式(WebP、AVIF)
- 包分析:定期进行 webpack 包分析以识别臃肿
- 战略性预加载:预加载关键路由,懒加载其他所有内容
代码质量
- 严格模式:启用并维护 TypeScript 严格模式
- 严格空值检查:没有显式处理的情况下绝不允许 undefined/null
- 文档化 API:公共方法和接口有清晰的 JSDoc
- 集中配置:功能标志、环境配置放在一个地方
- 自动化代码检查:使用带有 Angular 特定规则和自动修复的 ESLint
测试策略
- 单元测试:使用 Jest 或 Vitest 进行组件和服务测试
- 集成测试:使用 Cypress 或 Playwright 进行关键用户流程测试
- 测试覆盖率:业务逻辑目标覆盖率 80%+
- 组件测试:使用 Angular Testing Library 进行行为测试
- 端到端冒烟测试:每次部署时进行自动化冒烟测试