四、指令与管道:内置指令、自定义指令、纯管道与不纯管道、async管道

本节核心:掌握 Angular 内置指令的底层机制、自定义指令的设计模式、纯/不纯管道的变更检测差异,以及 async 管道的自动订阅/取消策略。面试中常以“实现一个自定义结构指令”或“管道为何不执行”为切入点,考察对变更检测和响应式编程的理解。
识别问题类型 内置/自定义/管道 核心概念 结构/属性/纯/不纯 代码实现要点 生命周期/变更检测 实际应用场景 权限/格式化/Observable 面试加分点 async自动取消 纯管道 纯函数,仅参数变化触发 不纯管道 每次CD都执行,注意性能 回答时按此路径组织:先分类,再解释机制,最后给代码示例

面试问答卡

面试问题

请解释 Angular 内置指令 *ngIf、*ngFor 和 *ngSwitch 的工作原理,并说明它们与自定义结构指令的区别。

面试官考察点

  • 对模板编译和嵌入式视图的理解
  • 结构指令的微语法(microsyntax)知识
  • 自定义指令时对 ViewContainerRef 和 TemplateRef 的掌握

回答框架

  1. 明确分类:结构指令(改变 DOM 结构)vs 属性指令(改变外观/行为)
  2. 阐述 *ngIf 通过内嵌 + 条件创建/销毁视图;*ngFor 通过 trackBy 优化性能
  3. 自定义结构指令需注入 TemplateRef 和 ViewContainerRef,在生命周期中调用 createEmbeddedView / clear

简短示例回答(约 90 字)

“*ngIf 将元素包裹于 ,通过条件决定是否实例化嵌入式视图。*ngFor 则遍历集合,支持 trackBy 避免全量销毁。自定义结构指令需注入 TemplateRef 和 ViewContainerRef,在 ngOnInit 或输入属性变化时调用 createEmbeddedView。区别在于内置指令经过微语法转换,自定义需手动处理生命周期。”

可能追问

  • “*ngIf 与 [hidden] 的性能差异?”
  • “如何实现一个带 else 模板的 *ngIf?”
  • “*ngFor 中 trackBy 如何减少 DOM 操作?”
避坑提醒: 不要在结构指令中同时使用多个星号前缀。回答时强调“微语法”将被 Angular 编译器转换为 ,避免被追问语法糖细节时卡壳。

面试问题

纯管道(pure pipe)和不纯管道(impure pipe)的区别是什么?在哪些场景下必须使用不纯管道?

面试官考察点

  • 对 Angular 变更检测(Change Detection)的理解
  • 纯函数概念在管道中的应用
  • 性能意识:不纯管道对性能的影响

回答框架

  1. 定义:纯管道仅在输入参数变化时重新计算(对象引用变化);不纯管道每次变更检测周期都执行
  2. Angular 对纯管道的优化:复用上一次结果,减少不必要的计算
  3. 必须使用不纯管道的场景:管道内部依赖外部状态(如当前时间)、数组内容变化但引用不变、复杂对象内部属性变化且无法创建新引用

简短示例回答(约 80 字)

“纯管道基于输入引用的变化检测,例如 DatePipe 只对日期值变化响应。不纯管道在每次变更检测时执行,例如自定义 'timeAgo' 管道需显示实时相对时间。使用不纯管道必须加 'pure: false' 装饰器,并注意通过 memoization 或限制执行频率避免性能问题。”

可能追问

  • “如何手动触发不纯管道重新计算?”
  • “pure: false 对组件性能的影响有多大?”
  • “为什么 async 管道被设计为不纯管道?”
避坑提醒: 多数面试者只记得“纯管道引用变化触发”,但忘记强调 Angular 通过 ChangeDetectionStrategy.OnPush 也会影响管道调用。回答时补充“在 OnPush 模式下不纯管道仍会执行”会更显深度。

面试问题

async 管道是如何实现自动订阅和取消订阅的?它与手动 subscribe/unsubscribe 相比有哪些优势?

面试官考察点

  • 对 RxJS Observable 生命周期的掌握
  • Angular 管道与组件销毁钩子的关联
  • 内存泄漏防范意识

回答框架

  1. async 管道在内部调用 subscribe,将 Observable 值传递给模板
  2. 管道在 ngOnDestroy 钩子中调用 unsubscribe,防止内存泄漏
  3. 优势:代码简洁、自动管理生命周期、支持 OnPush 变更检测策略(通过 markForCheck)
  4. 局限:多次使用 async 管道可能导致多次订阅,推荐使用 shareReplay 或异步策略

简短示例回答(约 100 字)

“async 管道是一个不纯管道,它订阅传入的 Observable 或 Promise,在值变化时触发变更检测。当组件销毁时,管道在其 ngOnDestroy 中取消订阅。相比手动 subscribe,它消除了忘记取消的风险,且配合 OnPush 策略时自动调用 changeDetectorRef.markForCheck(),确保视图及时更新。缺点是每个模板中的 async 产生独立订阅,建议配合 shareReplay 优化。”

可能追问

  • “async 管道在 OnPush 策略下如何工作?”
  • “同一个 Observable 在模板多处使用 async,会订阅几次?”
  • “async 管道如何处理 Promise 与 Observable 的区别?”
避坑提醒: 不要只说“自动订阅”,要解释底层订阅发生在变更检测周期内。注意区分 async 管道用于 Observable 时返回的是最新值,用于 Promise 时只返回一次。回答时最好接一个代码片段演示。

项目案例:电商后台权限管理系统

项目名称ECM 电商后台权限看板
业务背景为运营团队提供角色化数据看板,不同角色(超级管理员、品类经理、客服主管)展示不同模块和操作按钮。要求前端根据后端返回的权限列表动态渲染菜单和按钮,且权限变更时实时响应。
规模指标页面数 40+,指令数 15+(含自定义指令),管道数 8。活跃用户 500+,日均请求 2 万+。组件重用率 60%。
技术栈Angular 15、TypeScript、ngrx/store、RxJS、自定义指令 + 结构指令、async 管道、纯管道(日期、货币)、不纯管道(权限实时过滤)
核心难点
  • 权限绑定为 Observable,需在模板中指令内异步判断
  • 按钮级别权限需要 *appHasPermission 结构指令,但指令内部需订阅权限流
  • 权限数据变化时(如用户手动刷新),多个指令需同步更新,防止闪烁
面试高频追问
  • “自定义权限指令如何订阅 Observable 并自动取消?”
  • “如果权限列表通过 HTTP 请求获取,管道如何保证数据实时性?”
  • “多个 *appHasPermission 在同一个容器中,如何避免重复订阅?”
可迁移回答话术
“我在项目中使用 *appHasPermission 自定义结构指令,接收权限码字符串,内部注入 PermissionsService(返回 Observable),在 ngOnInit 中订阅并通过 ViewContainerRef 决定是否创建视图。配合 async 管道在父组件中订阅一次权限集合,通过 shareReplay(1) 避免子指令重复请求。指令销毁时自动取消订阅,确保了性能和内存安全。”
面试加分技巧: 提到自定义结构指令时,主动展示如何通过 set 输入属性接收权限码并响应变化;谈到 async 管道时,补充 changeDetectorRef.detach() 与 reattach() 的手动控制。这些细节能让面试官看到你对底层变更检测的理解深度。