Angular 核心与工程实践

共 17 题
📑 题目列表 17 题
#
★★★

1. Angular 的依赖注入容器与模块化体系(NgModule/Standalone)的演进,Standalone 何时必选?

Angular 的依赖注入容器与模块化体系是如何从 NgModule 演进到 Standalone 的?Standalone 组件在哪些场景下是必选方案?

  • NgModule 的声明、导入、提供者与导出四类职责
  • Standalone 组件自包含依赖(imports/providers)的形态
  • 引导方式差异(bootstrapApplication vs bootstrapModule)与必选场景

NgModule 是 Angular 14 引入 Standalone 之前的主要模块化单元,同时承担声明组件/指令/管道、导入依赖模块、配置依赖注入提供者与导出公共 API 四类职责,缺点是样板代码多、依赖边界隐式、不利于 tree-shaking。Standalone 组件自 Angular 15 起稳定,组件直接在 @Component 装饰器的 imports 数组中声明自身依赖、providers 中声明注入服务,不再需要 NgModule 包装,依赖边界显式清晰,Angular 19 起成为默认推荐形态。

Standalone 必选的场景包括:新项目默认全部使用(官方推荐路径);开发共享组件库或第三方库(避免强制消费方为单个组件创建 NgModule);微前端与按需加载场景(loadChildren 可以直接指向 Standalone 组件作为懒加载入口);以及希望减少样板、提升编译与摇树优化的项目。NgModule 仅在维护遗留代码、或确实需要其高级能力(如 useExisting 别名集中管理、统一导入导出的既有约定)时保留。

本题考察对 Angular 模块化演进主线的理解:从"集中式模块声明"走向"组件自包含依赖"。回答的关键是既讲清 NgModule 的职责构成,也讲清 Standalone 的边界优势与必选场景,体现对官方演进方向的把握。

// Angular 19 的 Standalone 引导方式
bootstrapApplication(AppComponent, {
  providers: [provideRouter(routes), provideHttpClient()]
});
#
★★★

2. Angular 变更检测(Zone.js vs Signals)的原理,onPush 策略如何正确使用?

Angular 的变更检测机制在 Zone.js 时代与 Signals 时代有何不同?onPush 策略的正确使用方式与常见误区是什么?

  • Zone.js 猴子补丁拦截异步 API 并触发全局变更检测的机制
  • Signals 细粒度依赖跟踪与 zoneless 变更检测
  • onPush 的触发条件、使用前提与常见误用

Zone.js 通过猴子补丁包装 setTimeout、Promise、事件监听等异步 API,任何异步回调执行完毕后都会从根组件开始自上而下运行变更检测,再结合脏标记与 onPush 剪枝减少检查范围。Signals 时代,组件模板读取信号时被记录为依赖,信号变化会精确标记关联组件及其 onPush 祖先为脏,不再需要全局扫描,配合 AsyncPipe 与 signal inputs 可以完全移除 Zone.js,进入 zoneless 模式。

onPush 的正确用法:组件只依赖 @Input 引用变化、Observable 发射或显式 markForCheck 时才会被检查,因此必须配合不可变数据(每次更新产生新引用)、AsyncPipe 订阅流式数据,并避免在服务里用可变对象内部修改后期望视图更新。常见误区:把 onPush 当作万能性能优化却仍直接修改数组或对象内部字段、依赖非 Observable 的服务状态,结果导致视图不更新,反而引入难排查的 bug。

本题核心是两个维度:"什么触发变更检测"(Zone.js 的异步钩子 vs 信号的通知)与"检查范围多大"(全局扫描 vs 精确到组件)。onPush 的要点在于理解它是"引用的契约",必须与不可变更新和流式订阅配套使用。

#
★★★

3. Angular 依赖注入的作用域层级(root/平台/组件)与 provide 函数的现代写法?

Angular 依赖注入的作用域层级有哪些?provide 系列函数的现代写法相比传统 providers 数组有哪些优势?

  • 注入器层级:平台级、root(环境)、模块级、组件级
  • provideIn: 'root' 的单例语义与 tree-shaking
  • provide 系列函数(provideRouter/provideHttpClient/provideEnvironmentInitializer 等)的现代写法

Angular 的注入器按层级组织:最顶层是平台注入器,往下是 root(环境)注入器,再往下是按需创建的模块注入器与每个组件/指令各自的组件注入器。依赖解析自底向上回溯:组件级提供者创建独立实例并随组件销毁,root 级提供者默认应用级单例;provideIn: 'root' 让服务可被摇树优化,未被使用时不进入产物。

传统写法 providers: [{ provide: SomeToken, useClass: Impl }] 中 useClass/useFactory/useValue/useExisting 为对象字面量形式,分散在各处、可组合性弱;Angular 14+ 引入 provide* 系列函数(provideRouter、provideHttpClient、provideEnvironmentInitializer、provideAppInitializer 等),作为独立工厂函数可在环境注入器与特性配置中统一使用,提供更严格的类型检查与可组合性,是现代写法的推荐形态。

本题考察 DI 的"层级模型"与"写法演进"两个层面:先讲清解析顺序与实例生命周期(root 单例、组件级随组件销毁),再讲 provide 函数相比 useXxx 字符串键写法的类型安全优势,体现对官方 API 演进的跟随。

@Component({
  providers: [
    { provide: API_BASE_URL, useValue: '/api' },
    { provide: Logger, useFactory: () => new Logger('app') }
  ]
})
export class AppComponent {}
#
★★

4. Angular 19+ 的模板新语法,@let 局部变量、@if/@for/@defer 控制流与事件重放(event replay)在企业模板升级中的工程价值?

Angular 19+ 的 @let 局部变量、@if/@for/@defer 内置控制流与事件重放(event replay)有哪些工程价值?企业模板升级时如何评估与落地?

  • 内置控制流语法的性能与可读性收益
  • @let 局部变量的作用域与减少重复计算
  • @defer 的懒加载状态管理与 event replay 对 SSR 交互体验的提升

Angular 17 起内置控制流语法成为默认:@if/@else 无需 ng-template 包装,@for 内置 track 表达式自动优化列表 DOM 复用,@switch 语义更清晰,类型收窄也更准确;@let 允许在模板中定义局部变量(如管道结果的复用),避免同一表达式重复计算与重复订阅。@defer 按触发条件(viewport、idle、interaction 等)懒加载组件块,配合 placeholder、loading、error 三个状态模板控制加载体验,可直接削减首屏包体。

事件重放(event replay)面向 SSR/SSG 场景:浏览器在水合完成前捕获用户的点击等事件并缓存,水合完成后按序重放,解决传统 SSR 中"水合前点击无效"导致的用户等待与困惑,是 Angular 18+ 水合配置(withEventReplay)提供的工程化能力。企业升级评估要点:模板迁移可由 ng update 自动完成、track 收敛提升长列表性能、defer 针对重组件做懒加载收益量化、event replay 配合 SSR 提升 TTI 体验。

本题考察对模板层新特性的整体认知。回答应分层:控制流与 @let 解决"模板表达力与性能",@defer 解决"组件加载时机",event replay 解决"SSR 水合前交互",并落到企业升级的评估(自动化迁移、性能量化、渐进试点)。

#
★★

5. RxJS 在 Angular 中的核心地位,表单、路由守卫与状态流如何用 Observable 组织?

RxJS 在 Angular 中处于什么地位?表单、路由守卫与全局状态流分别如何用 Observable 组织?

  • 响应式表单 valueChanges/statusChanges 的流式表达
  • 路由守卫返回 Observable 的异步鉴权
  • 服务层 Subject/BehaviorSubject 状态流与 AsyncPipe 自动订阅

RxJS 是 Angular 响应式编程的基石,框架大量能力以 Observable 暴露:HttpClient 请求、路由参数(ActivatedRoute.params/queryParams)、响应式表单(valueChanges、statusChanges)都是流。表单场景用 valueChanges 做联动计算、debounceTime 做输入防抖、异步校验用 switchMap 切换请求;路由守卫(CanActivate)可以返回 Observable,用 combineLatest 组合多个异步条件完成鉴权,支持随时发出结果。

全局状态通常收敛在服务中:BehaviorSubject 保存当前快照并广播新值,组件通过 async 管道订阅(自动退订、与变更检测协作),需要派生状态时用 combineLatest/map 组合。核心心智是把"随时间变化的值"建模为流,用操作符表达时序与变换,组件只消费流而不管理订阅生命周期。

本题考察 RxJS 与 Angular 各能力的贯穿性认知。回答覆盖三类典型场景(表单、守卫、状态流)并点出共同点:声明式流 + 操作符组合 + 自动订阅管理,体现"流式思维"。

#
★★

6. Angular 表单(模板驱动/响应式)的校验与性能要点?

Angular 模板驱动表单与响应式表单在校验机制上有何差异?性能要点有哪些?

  • 模板驱动(ngModel)与响应式(FormControl/FormGroup)的模型差异
  • 同步/异步校验器与 ValidatorFn 的组合
  • 大表单的订阅粒度、防抖与 onPush 配合

模板驱动表单基于 ngModel 与模板引用变量,校验通过内置指令(required、minlength、自定义指令)在模板中声明,模型隐式、适合简单表单;响应式表单在组件类中显式构建 FormControl/FormGroup/FormArray,校验器是纯函数(Validators.required、Validators.pattern 及自定义 ValidatorFn),可组合、可复用、可单元测试,并支持异步校验器(AsyncValidatorFn),适合复杂与动态表单。

性能要点:大型表单避免在每次输入时对整棵表单树做全量校验与状态遍历,可只订阅相关字段的 valueChanges 并配合 distinctUntilChanged/debounceTime 降低频率;异步校验用 switchMap 取消过期请求防止竞态;组件使用 onPush 时确保表单模型更新走新引用(setValue/patchValue 或不可变替换);组件销毁时用 takeUntilDestroyed 清理订阅,避免内存泄漏。

本题考察两种表单范式的定位差异与工程化性能意识。回答先对比模型与校验器形态,再给出可操作的性能清单(订阅粒度、防抖、异步竞态、onPush、清理),体现实战经验。

#
★★

7. Angular 的懒加载、预加载与渲染模式(SSR/Prerender)如何选型?

Angular 的懒加载、预加载策略与 SSR/Prerender 渲染模式应如何选型?

  • loadChildren 路由懒加载与 chunk 拆分
  • PreloadAllModules 与自定义预加载策略
  • CSR/SSR/Prerender/hybrid 渲染模式的取舍依据

懒加载通过路由配置 loadChildren: () => import('./path') 把模块/独立组件拆分为独立 chunk,首屏只加载必需代码,还可配合 canLoad/守卫按权限决定是否加载。预加载在懒加载基础上优化后续导航体验:PreloadAllModules 在空闲时预取全部路由,自定义 PreloadingStrategy 可根据网络状态、用户行为或路由权重选择预加载子集,平衡带宽与体验。

渲染模式选型:内容型、SEO 敏感、公开访问的站点用 Prerender(SSG)在构建期输出静态 HTML,成本最低、CDN 友好;需要动态数据、登录态与实时内容用 SSR(配合 provideClientHydration 水合与 transferState 避免重复请求);强交互、后台类应用默认 CSR 即可。Angular 17+ 的混合形态(SSR 全量 + @defer 客户端懒加载)可以在 SSR 之上进一步控制客户端加载。决策依据是内容更新频率、SEO 需求、TTI 预算与部署形态的组合。

本题考察"加载策略"与"渲染模式"两个维度的组合决策。回答需给出可操作的判断逻辑:按内容时效性选渲染模式、按首屏预算选懒加载、按后续导航体验选预加载策略。

#
★★

8. Angular 控制流语法(@if/@for/@defer)与旧 *ngIf/*ngFor 的性能差异?

Angular 内置控制流语法 @if/@for/@defer 与旧的 *ngIf/*ngFor 结构指令在性能上有哪些差异?

  • 结构指令基于 ng-template 的运行时包装与额外指令实例开销
  • @for 内置 track 的 DOM 复用与移动优化
  • @defer 的块级懒加载与加载状态管理

旧结构指令 *ngIf/*ngFor 在运行时通过 ng-template 实例化内嵌视图并创建指令实例,每次表达式变化都涉及内嵌视图的创建/销毁,*ngFor 若不提供 trackBy 会在列表变化时重建大量 DOM。内置控制流把逻辑下沉到编译器:@if 直接生成条件渲染指令,少了 ng-template 包装层;@for 把 track 作为内置语法,自动复用 DOM、识别移动与增删,避免手工 trackBy 遗漏导致的性能退化;@switch 语义更清晰且无额外包装。

@defer 的差异更大:它以组件块为单位做懒加载,可显著减少首屏渲染与包体,配合 placeholder/loading/error 状态与 prefetch/afterRender 触发时机,实现精细的加载策略。在大型列表与复杂模板下,这些差异可以量化为渲染耗时与内存占用的下降;小模板场景差异不明显,但内置控制流在可读性与类型收窄上仍然更优。

本题考察"编译器优化 vs 运行时指令"的性能差异根源。回答要点是:旧语法依赖运行时微指令实例化,新语法在编译期确定优化,@for 的 track 内置与 @defer 的块级懒加载是差异最大的两点。

#
★★

9. Angular SSR 与 Hydration、Event Replay 的工程配置?

Angular SSR 与客户端水合(Hydration)如何配置?Event Replay 解决什么问题?

  • provideClientHydration 与增量水合配置
  • Event Replay 缓存并重放水合前的用户交互
  • transferState 数据传递与避免水合不一致

使用 ng add @angular/ssr 或在应用搭建时引入 Angular SSR 支持,服务端渲染输出 HTML;客户端在 bootstrapApplication 的 providers 中加入 provideClientHydration() 开启水合,Angular 会复用服务端生成的 DOM 而非重建。Angular 18+ 提供增量水合(withIncrementalHydration)与事件重放(withEventReplay):浏览器在 hydration 完成前捕获用户点击等事件存入队列,水合完成后按序重放,让"水合前交互"不再丢失,显著改善大应用在弱网/低端设备上的可交互体验。

工程配置要点:通过 withConfig 按区域/组件控制水合粒度;用 TransferState 把服务端请求的数据传递到客户端,避免重复请求;模板中避免输出服务端与客户端不一致的内容(当前时间、随机数、直接读 window/document 的值),否则水合后视图会被覆盖并产生告警。SSR 构建会同时产出浏览器与服务端 bundle,部署时按平台(Node server 或 serverless adapter)配置。

本题考察 SSR 落地的具体配置面:开启水合、配置事件重放、用 transferState 保一致。回答应包含可执行的配置步骤与常见坑(水合不一致),体现工程经验。

#
★★

10. Angular 的 DI 体系,providers 层级与注入令牌?

Angular 依赖注入体系中的 providers 层级与注入令牌(InjectionToken)是如何工作的?

  • 注入器层级与自底向上的解析顺序
  • 类/值/工厂/别名四类提供者写法
  • InjectionToken 对非类依赖与跨库配置的标识作用

Angular 中每个组件都有独立注入器,解析依赖时从当前注入器开始自底向上查找,同级组件之间不可互见,父组件提供者对其所有子组件可见。提供者有四种形态:类提供(useClass)、值提供(useValue,常用于配置对象)、工厂提供(useFactory,可依赖其他服务并做条件创建)、别名提供(useExisting,多 token 指向同一实例)。

InjectionToken 用于标识非类类型的依赖:当依赖是字符串、配置对象或接口时无法用类作 token,InjectionToken 提供唯一标识并可携带类型信息与工厂默认值,常配 providedIn: 'root' 实现全局可注入且可摇树。现代还可用 provide* 系列函数(provideRouter、provideHttpClient 等)组织特性级配置,token 与提供者分离也让跨库配置与覆盖(测试中 override)更清晰。

本题考察 DI 的底层模型:层级解析顺序、四类提供者、token 机制。回答要突出"token 与实现分离"的思想,并说明 InjectionToken 在配置注入与库开发中的价值。

export const API_BASE = new InjectionToken<string>('API_BASE', {
  providedIn: 'root',
  factory: () => '/api/v1'
});
// 注入:inject(API_BASE)
#

11. Angular 18+/20 的新特性(Control Flow、defer、Signals 全链路)对企业项目升级的影响?

Angular 18+/20 的 Control Flow、defer、Signals 全链路新特性对企业项目升级有何影响?

  • 新特性清单与版本节奏(17 内置控制流、18 zoneless、19 standalone 默认/@let、20 signals 全链路)
  • ng update 自动化迁移与 codemod
  • Zone.js 移除对既有代码的影响评估

Angular 17 起内置控制流(@if/@for/@defer)成为默认,18 引入 zoneless 实验支持与更细的水合配置,19 使 Standalone 成为默认、新增 @let 与事件重放稳定版,20 深化 Signals 全链路(signal inputs/outputs/queries、model()、信号表单与 zoneless 全面化)。对企业项目,这些特性降低样板与包体、提升运行时性能,但升级需要系统评估:用 ng update 执行官方自动迁移(控制流迁移、standalone 迁移、类型化表单迁移),逐条核对 changelog 中的破坏性变更,检查第三方库对新版本的兼容声明。

风险最大的点在于移除 Zone.js:依赖 Zone 打补丁的旧代码(在 setTimeout/Promise 回调中修改组件字段)在 zoneless 下不再自动触发变更检测,需要改造为信号、AsyncPipe 或显式通知。建议路线:先小范围试点(新模块用新特性),建立自动化测试与性能基线,再按模块渐进迁移。

本题考察对 Angular 演进节奏与升级工程的理解。回答既要列举新特性(证明熟悉),更要给出升级影响评估方法(迁移工具、破坏性变更清单、Zone 依赖排查、渐进试点)。

#

12. Angular 表单(响应式)的 ValueChanges 与异步校验的性能陷阱?

Angular 响应式表单的 valueChanges 与异步校验存在哪些性能陷阱?如何规避?

  • valueChanges 每次输入变化高频发射带来的重计算与请求问题
  • 异步校验未取消导致的竞态与内存泄漏
  • debounceTime/distinctUntilChanged/switchMap 的缓解手段

valueChanges 在用户每次输入时都会同步发射新值,若订阅中执行重计算、发起 HTTP 请求或触发整表状态更新,会造成明显的卡顿与冗余请求。缓解方式:用 debounceTime 合并高频输入、distinctUntilChanged 去重、filter 限制触发条件,并把重计算收敛到派生流或 memo 化函数中。

异步校验的典型陷阱是竞态:用户快速连续输入时多个校验请求并发,先发后至的结果覆盖新结果,导致错误提示与输入不同步。解法是订阅链中使用 switchMap(只保留最新请求)而非 mergeMap,并配合 takeUntilDestroyed 在组件销毁时取消订阅;同时避免在异步校验回调中修改表单值造成循环。onPush 组件配合字段级订阅(只订阅相关 control)能进一步缩小变更检测范围。

本题考察"流的高频发射 × 异步管理"的组合问题。回答要点是识别两类陷阱(高频重计算、异步竞态/泄漏)并给出对应的操作符级解决方案,体现 RxJS 实战能力。

#

13. Angular 的信号(Signals),响应式新机制?

Angular 的信号(Signals)是什么?它作为新的响应式机制如何工作?

  • signal()/computed()/effect() 三个原语的语义
  • 读取收集依赖、写入通知更新的细粒度机制
  • 信号与 zoneless 变更检测及 RxJS 互操作的关系

信号是 Angular 引入的细粒度响应式原语:signal() 创建可写状态(读操作被依赖收集,写操作通知订阅者);computed() 声明派生值,惰性求值并缓存,仅依赖变化时重算;effect() 声明副作用,自动跟踪其读取的信号,依赖变化时重新执行。在模板中读取信号会记录依赖,信号变化时 Angular 精确标记相关组件,不再需要全树检查,这是 zoneless 变更检测的基础。

信号与既有生态可互操作:toSignal/toObservable 在信号与 RxJS 流之间转换;signal inputs、model() 把响应式能力扩展到组件输入与双向绑定;信号表单(signal-based forms)把表单状态也信号化。设计上信号强调"细粒度、可追踪、无 Zone 依赖",但需要注意 effect 中避免写信号造成循环、computed 应保持纯函数。

本题考察信号原语的核心语义。回答围绕"读收集/写通知"机制展开,并说明信号在模板、组件边界与 zoneless 中的角色,突出其与 Zone.js 全局检查的本质区别。

const count = signal(0);
const doubled = computed(() => count() * 2);
effect(() => console.log('count =', count()));
count.set(1); // 触发 doubled 重算与 effect
#

14. Angular 的变更检测,Zone.js 与 zoneless?

Angular 的变更检测中 Zone.js 与 zoneless 模式有什么区别?

  • Zone.js 拦截异步任务后触发全局变更检测的机制
  • zoneless 依赖信号与 AsyncPipe 的按需通知
  • 迁移到 zoneless 的前提条件与收益

Zone.js 通过对浏览器异步 API(事件、定时器、Promise、XHR 等)打补丁,在异步任务完成时自动触发从根组件开始的变更检测,配合脏标记与 onPush 剪枝减少检查量;优点是"无需显式通知",缺点是即使数据没变化也会跑一轮检查,且需要额外打补丁包体。zoneless 模式移除 Zone.js:只有信号写入、AsyncPipe 的 Observable 发射、事件处理器等显式来源才会触发对应组件的检查,检查范围更小、包体更小、可预测性更强。

迁移到 zoneless 的前提:组件不应在异步回调中直接修改未用信号/AsyncPipe 包装的可变内部状态,服务层状态应信号化或流化;全面采用 signal inputs、AsyncPipe、computed 表达依赖;依赖 Zone 调试信息(如 devtools 中的 zone 栈)的工具需要调整。收益主要体现在首屏包体(去掉 zone.js)与运行时检查开销的下降。

本题考察变更检测范式的转变:从"异步后全局扫描"到"依赖驱动的按需通知"。回答需对比触发来源、检查范围与迁移条件,体现对 Angular 架构演进的理解。

#

15. Angular 的模块与独立组件,NgModule vs standalone?

Angular 的 NgModule 与 standalone(独立组件)有何区别?实践中如何选择?

  • NgModule 的声明/导入/提供者/导出职责与样板问题
  • standalone 组件自包含 imports/providers 的形态
  • 默认化趋势与渐进迁移策略

NgModule 是集中式的模块单元:声明组件/指令/管道、导入依赖、提供服务、导出公共 API,适合早期集中管理,但样板多、依赖关系隐式、对 tree-shaking 不友好。standalone 组件把依赖声明内聚到组件自身:@Component 的 imports 数组声明模板依赖、providers 声明注入服务,组件可以独立编译、单独懒加载,依赖边界显式,也便于库开发(消费方无需建模块),并通过 bootstrapApplication 直接引导应用。

选择策略:新代码默认 standalone(Angular 19 起官方默认推荐);既有 NgModule 项目通过 ng generate 或迁移工具按模块渐进转换,模块内依赖关系可以逐步拆解;保留 NgModule 的场景是维护遗留代码、需要其集中式能力(如统一 useExisting 别名、跨模块批量导出)或第三方库仍以模块形态发布。两者可以混用:standalone 组件可以声明在 NgModule 中,NgModule 也可以被 standalone 组件 import。

本题考察模块化形态的对比与迁移判断。回答要给出"默认 standalone、按需保留 NgModule"的清晰结论,并说明混用兼容性,体现对官方演进方向与兼容性的把握。

#

16. Angular 的 RxJS,响应式状态与异步处理?

Angular 中如何使用 RxJS 进行响应式状态管理与异步处理?

  • 服务中 BehaviorSubject/Subject 状态流与派生流
  • HttpClient 返回 Observable 与拦截器
  • AsyncPipe 自动订阅与 toSignal 互操作

Angular 中 RxJS 状态管理的标准形态是"服务 + 流":服务内用 BehaviorSubject 保存状态快照,暴露 asObservable() 只读流,结合 scan、combineLatest、map 等操作符派生组合状态;组件通过 async 管道订阅,自动完成订阅与退订并驱动变更检测。异步处理方面,HttpClient 的所有请求返回 Observable,配合拦截器(token 注入、错误统一处理)、retry/retryWhen 重试、finalize 清理 loading 状态,形成完整的请求生命周期管理。

Angular 16+ 提供 toSignal/toObservable 在信号与 RxJS 之间互转:组件内部状态用信号表达,跨组件与异步编排保留 RxJS 流,两者在模板与 DI 中无缝混用。实践要点:流命名遵循 $ 约定、避免在组件里手动 subscribe 后又忘记退订、用 takeUntilDestroyed 替代手动清理,把异步逻辑收敛到服务层保持组件可测试。

本题考察 RxJS 在 Angular 中的工程化组织方式。回答覆盖状态流、异步请求与互操作三个面,突出"服务层管流、组件层消费流、AsyncPipe 管生命周期"的分层思想。

#

17. Angular 的全局错误处理,自定义 ErrorHandler、Zone 错误与 signals 时代错误边界的设计?

Angular 的全局错误处理机制是怎样的?在 signals 时代错误边界应如何设计?

  • ErrorHandler 的全局捕获职责与自定义实现
  • Zone 错误传播与 signals 时代 effect/computed 的错误处理
  • 错误边界设计:记录上报与 UI 降级分层

Angular 通过 ErrorHandler 提供全局错误钩子:默认实现把未处理异常输出到 console,自定义 ErrorHandler 可以对接监控平台(上报堆栈、用户与路由上下文),实现对应用级异常的集中记录。但 ErrorHandler 无法渲染 UI 降级,因此错误处理需要分层:ErrorHandler 负责"记录与上报",错误边界(error boundary)负责"UI 降级"。Zone 时代,异步回调中的错误会冒泡到 Zone 的错误处理并被统一捕获;移除 Zone 后,错误在各自异步上下文传播,需依靠信号计算(computed)抛错时的保护与 effect 内的 try/catch 自行收敛。

signals 时代的边界设计:模板层用 @if 包裹危险区域并提供 fallback 呈现;@defer 提供 error 状态处理懒加载失败;自定义"错误边界"组件用信号状态(error 信号)承载错误并渲染降级视图,通过重试信号重置;同时保留 ErrorHandler 做日志聚合。设计原则是"错误可观测、降级可恢复、边界按业务模块划分"。

本题考察错误处理的分层思维:记录上报(ErrorHandler)与 UI 降级(边界组件)分离,并注意 Zone 移除后异步错误传播方式的变化,体现对 signals 时代工程挑战的认知。