CHARLIE SAYS

查理如是说
DATE 2026-08-24
THEME
SERIES / ANGULAR / P-214 · Angular 高级教程

Angular 全家桶 48:大杂烩——杂项技巧与经验合集

系列写到这里,主干知识已经铺完。最后一篇实战向的文章不设单一主题,而是把散落在日常开发里的”边角利器”与经验汇总成册:内容投影的进阶技巧、宿主元素样式、Angular Elements、DevTools 与 AI 工具链、性能清单、报错速查。当作工具书用,遇到问题回来翻。

ng-content 的 select 技巧

内容投影不只是”把内容搬进来”,select 属性支持按元素、属性、 class 精准分拣:

<!-- 卡片组件 -->
<div class="card">
  <header><ng-content select="[card-header]" /></header>
  <main><ng-content /></main>
  <footer><ng-content select="[card-footer], .footer" /></footer>
</div>
<app-card>
  <span card-header>标题</span>
  <p>正文默认进入无 select 的插槽</p>
  <div class="footer">底部</div>
</app-card>

常用套路:

  • select="[slot]" 属性选择器最稳,不受用户改 class 影响
  • 投影组件的样式隔离:投影内容声明在父组件作用域,子组件样式作用不到它们——需要跨边界时用 ::ng-deep(已废弃但仍在)或全局样式
  • 默认内容@if 包住 <ng-content> 无法判断”是否投影了内容”,改用两段式——有内容投影时 ng-content 优先,空态用兄弟节点兜底,或通过 @ContentChild 检测
  • 多插槽必须全部有 select:没有 select 的插槽只能有一个,它是”其余内容”的收容所

对比 ng-template + NgTemplateOutlet 的”显式插槽”方案(第 10 篇):ng-content 是声明式、宿主写什么就渲染什么;ng-template 是宿主把”怎么渲染”的模板交给组件,组件决定渲染时机与次数。表单控件 label/错误提示这类强结构场景用后者。

host 类与 :host 上下文

给组件宿主元素本身加样式与状态类:

@Component({
  selector: 'app-tile',
  host: {
    class: 'tile',
    '[class.expanded]': 'expanded()',
    '[attr.aria-expanded]': 'expanded()',
  },
})
export class TileComponent {
  readonly expanded = signal(false);
}

样式侧的配套选择器:

:host {
  display: block; // 宿主默认 inline,自定义元素几乎总是要 block/flex
}

:host(.expanded) {
  border-color: var(--accent);
}

// 祖先链上的上下文:父组件带 .dark 时才生效
:host-context(.dark) {
  background: #1e1e1e;
}

host 对象替代了老的 @HostBinding/@HostListener 装饰器(v22 中已不建议),事件绑定同样支持:'(click)': 'toggle()'。优先用 host 而不是在模板里包一层 div——少一层 DOM,Shadow DOM 仿真下也没有样式穿透问题。

@angular/elements:组件打包成 Web Component

把 Angular 组件注册成自定义元素,供非 Angular 页面(CMS、微前端壳、老技术栈)使用:

import { createCustomElement } from '@angular/elements';
import { NgElement, WithProperties } from '@angular/elements';

@NgModule({ imports: [RatingComponent] })
export class RatingElementModule implements DoBootstrap {
  private readonly injector = inject(Injector);

  ngDoBootstrap(): void {
    // inputs 自动映射为 attributes/properties
    const element = createCustomElement(RatingComponent, { injector: this.injector });
    customElements.define('app-rating', element);
  }
}
// 宿主页面(可以是纯 HTML)
const rating = document.querySelector('app-rating') as NgElement & WithProperties<RatingComponent>;
rating.value = 4; // 组件的输入
适合不适合
向外输出嵌入式小组件(评分、地图、播放器)整页应用——直接用 Angular
与老技术栈共存的部分增量 UI高频跨边界通信的复杂交互

成本注意:每个元素化组件都要带一份注入器上下文;信号输入映射为 property,attribute 只支持字符串。细节见第 16 篇的对比分析。

DevTools:信号图与 DI 图

Angular DevTools 的浏览器扩展在 v22 迎来两大杀手锏:

  • 信号图:可视化 signal/computed/effect 的生产者-消费者关系,定位”谁更新了它""为什么没更新”——信号应用的调试从猜谜变成看图
  • DI 图:可视化依赖注入关系与 provider 覆盖链,排查”为什么注入了两个实例”一目了然

配合 chrome-devtools-mcp,可以让 AI 代理直接读取 DevTools 状态(组件树、信号图)做诊断。本地排障三部曲:Profiler 录制找慢组件 → 信号图找多余依赖 → DI 图找实例异常。

AI 工具链一览

v22 生态的 AI 辅助已工具化:

  • 官方 skillsnpx skills add https://github.com/angular/skills 把 Angular 官方最佳实践安装进代理的技能库,生成代码遵循当前版本惯例
  • WebMCP(实验):通过 declareExperimentalWebMcpTool 把网页上下文暴露给 MCP 客户端,AI 能”看见”运行中的应用状态,做 E2E 级诊断
  • chrome-devtools-mcp:AI 读取性能轨迹与 DOM,配合 Angular DevTools 数据定位渲染问题

用法心态:AI 工具链加速的是”查证与定位”,架构决策与版本事实仍以 angular.dev 文档为准。

性能优化清单

按投入产出排序的检查项:

  • 长列表用虚拟滚动(第 43 篇),@for 全员带 track
  • 大计算用 computed 缓存,重活进 Web Worker
  • 图片懒加载:loading="lazy" + @defer 包裹首屏外组件(第 8 篇)
  • 包体:按路由懒加载 standalone 组件,@defer 拆首屏;检查 ng build 的 chunk 分析
  • 请求:httpResource 声明式缓存与去重(第 23 篇),防抖搜索用 debounced 信号
  • 变更检测:确认无 Eager 策略组件;事件处理器里只写信号,不手动改 DOM
  • 内存:退订 RxJS(takeUntilDestroyed)、清理 ResizeObserver/Interval(第 27 篇)
  • SSR:增量 hydration 默认开启,验证首屏指标(LCP/INP)与水合错误
  • 字体与图标:图标按需(第 39 篇),避免整包字体下载

常见报错速查

报错原因解法
NG0203: must be used in an injection context在普通函数/方法里调 inject()移到构造函数/字段初始化器,或改 @Inject 参数注入
NG0602: ... enter the view tree 之类 Portal 错误TemplatePortal 缺 ViewContainerRefattach 前补上第二个参数(第 41 篇)
NG05105(模板表达式类)信号没调用模板写 count() 而非 count
ExpressionChangedAfterItHasBeenChecked生命周期里同步改了已检查的绑定改信号驱动或移到 effect
NG0200: Circular dependency in DI服务互相注入抽第三个服务或 inject(Injector) 延迟取
Cannot bind to 'x' since it isn't a known propertystandalone 组件没 import 指令所在模块imports 数组补齐
NG0303: No provider for Xxx未注册 provider@Service() 确认 providedIn 或组件级 providers
Maximum call stack size exceeded 伴随循环更新effect 里写信号形成环拆分状态或用事件驱动(第 21 篇)

报错编号(NGxxxx)在 angular.dev 的 errors 手册都有专页,搜索编号永远比搜报错全文可靠。

系列导航

← CDK 22+ 教程 47:Layout 断点与 Observers 观察者 目录 Angular 全家桶 49:总结与进阶路线图 →
← 返回文章列表