第4章 依赖注入与服务模式面试

核心考点浓缩: DI原理 → Provider配置(useClass / useValue / useFactory / useExisting) → 单例 vs 工厂 → 可摇树优化(providedIn: 'root' / 'any')。本章覆盖80%的Angular架构师DI面试原题。
面试站位提示: DI是Angular的“脊柱”,面试官会从“如何控制服务作用域”到“Tree‑shakable providers”层层追问。建议先用一句话说清DI本质,再用Provider配置展示技术深度。
--- DI 面试框架 Injector 层级 4种Provider 单例 vs 工厂 providedIn 摇树 @Injectable() 优化 --- ### 面试问答卡片 ① **面试问题:** 请解释 Angular 依赖注入的原理,并说明 Injector 层级的作用。 | 考察点 | 说明 | |--------|------| | 底层理解 | 是否清楚DI是“实例化+生命周期+作用域”管理器 | | 层级概念 | 能否区分 ModuleInjector / ElementInjector / NullInjector | | 实际经验 | 是否遇到过多层级注入的坑(如循环依赖) | **回答框架:** DI本质 → Injector树结构 → 查找优先级(自下而上,直到NullInjector抛出错误) → 举例说明(如组件内注入服务但未提供,会向上查找)。 **示例回答:** “Angular DI 通过 Injector 树管理依赖。组件自身 Injector 未提供服务时,依次向父模块、根模块查找,最终到 NullInjector 抛错。我曾在懒加载模块中将服务写在 forChild 内,造成服务实例分裂,改用 providedIn: 'root' 解决。” **可能追问:** - ModuleInjector 和 ElementInjector 的区别? - @Injectable({providedIn:'platform'}) 与 'root' 的区别? **避坑提醒:** 不要只说“依赖注入就是传参”,要强调 Injector 树和 singleton 归属。避免把 providedIn 和 providers 混为一谈。 --- ### 面试问答卡片 ② **面试问题:** Provider 有哪几种配置方式?实际开发中如何选择? | 考察点 | 说明 | |--------|------| | 配置多样性 | 是否全面掌握 useClass / useValue / useExisting / useFactory | | 场景适配 | 能否根据配置化、Mock、动态参数选择合适方式 | | 运行效率 | 是否了解 useExisting 的别名复用机制 | **回答框架:** 列举4种方式 → 分别给出典型场景(useClass 默认,useValue 用于配置对象,useExisting 用于别名/接口多态,useFactory 用于需依赖其他服务才可构造的情况) → 总结选择原则。 **示例回答:** “useClass 最常用,如 ApiService;useValue 适合配置常量;useExisting 用于日志服务别名;useFactory 用于需要动态参数或依赖多个服务时才实例化的场景。例如我曾在环境配置中使用 APP_CONFIG = { apiUrl: '...' } 注入。” **可能追问:** - useFactory 中 deps 参数怎么写? - useClass 和 useExisting 在多模块下实例相同吗? **避坑提醒:** 不要在懒加载模块中使用 useClass 配置同一服务,会导致多实例;如果期望多实例,故意在模块的 providers 中声明。另外 useFactory 注意闭包陷阱。 --- ### 面试问答卡片 ③ **面试问题:** 如何实现单例服务和工厂服务?Angular 的可摇树优化如何生效? | 考察点 | 说明 | |--------|------| | 单例控制 | 是否知道 providedIn: 'root' 与根模块 providers 的区别 | | 工厂模式 | 能否用 useFactory 或 @Injectable() 配合 factory 函数 | | 树摇优化 | 理解 Tree‑shakable providers 的编译原理(dead code elimination) | **回答框架:** 单例:providedIn: 'root'(推荐) vs 根模块 providers(传统) → 工厂:useFactory 或提供工厂类 → 摇树优化:未引用的服务不会被打包;providedIn 使编译期直接分析引用关系。 **示例回答:** “使用 @Injectable({providedIn: 'root'}) 声明单例,Angular CLI 在构建时会自动移除未被任何组件或模块注入的服务代码。若需多实例,在组件的 providers 声明。工厂方式用 useFactory: (dep) => new Service(dep),并声明 deps。” **可能追问:** - providedIn: 'any' 与 'root' 的区别? - 如何强制实现模块级单例(只在某懒加载模块内唯一)? **避坑提醒:** 不要在全模块 providers 和 providedIn 混用时忘记重叠;懒加载模块中用 providers 会导致子模块多实例。摇树优化要求服务不被直接字符串引用,尽量使用类 。 ---
⚠️ 常见追问变形: “如果两个懒加载模块都提供了同一个服务,它们的实例是否相同?” 答案:实例不同,除非该服务声明为 providedIn: 'root'。面试官常用此测试你对作用域的理解。
--- ## 项目案例 **项目名称:** 企业级权限管理系统(RBAC + 可配置菜单) **业务背景:** 为集团公司内部开发的多租户权限中心,需统一管理角色、菜单、按钮级权限。前端需要支持动态菜单生成、权限指令、服务层统一鉴权。 **规模指标:** - 共计 28 个功能模块(14 个懒加载模块) - 日均请求 12 万次,权限校验服务被 170+ 组件注入 - 代码量约 45 万行 **技术栈:** Angular 15 + NgRx + Angular Material + 自定义指令 + 工厂模式 Provider **核心难点:** 1. 多租户下权限配置动态变化,需要 useFactory 在应用启动时从后端拉取权限数据然后注入。 2. 避免权限服务在懒加载模块中产生多个实例,需统一 providedIn: 'root'。 3. 摇树优化:未使用的权限指令、服务自动从 dist 包中剔除。 **面试高频追问:** - Q:权限服务中的 useFactory 如何设计? - Q:动态菜单组件如何利用依赖注入实现多态? - Q:如何保证权限服务在微前端场景下也是单例? **可迁移回答话术:** “我在权限系统中用 useFactory 动态配置权限服务:`useFactory: (config) => new PermissionService(config),deps: [APP_CONFIG]`,然后 APP_CONFIG 在根模块用 useValue 注入。所有懒加载模块共用同一个 PermissionService 实例,因为 providedIn: 'root' 保证单例。对于摇树优化,我们将权限指令中未使用的角色判断分支通过 Angular CLI 的 sideEffects 标记剔除。” --- useFactory 追问链 deps 依赖顺序 multi: true 合并 异步依赖处理 APP_INITIALIZER + APP_INJECTOR ---

Provider 配置类型速查表

配置类型 典型用法 实例数 适用场景
useClass { provide: ApiService, useClass: MockApiService } 每个 Injector 一个 Mock、环境切换
useValue { provide: APP_CONFIG, useValue: { apiUrl } } 常量副本 配置、常量
useExisting { provide: Logger, useExisting: ConsoleLogger } 共享单例 别名、多态
useFactory { provide: MyService, useFactory: (dep) => ..., deps: [Dep] } 按工厂逻辑/次 动态构造、依赖多服务
面试加分点: 提到 providedIn: 'platform' 可让服务在微前端的不同 Angular 实例间共享;提到 @Injectable() 的 providedIn: 'any' 在每个懒加载模块中创建独立实例(与 'root' 相反)。