首页
/ Angular NG02200 Missing Iterable Differ 详解:*ngFor 为什么无法遍历字符串与对象

Angular NG02200 Missing Iterable Differ 详解:*ngFor 为什么无法遍历字符串与对象

2026-09-07 12:00:56作者:宗隆裙

NG02200 是 Angular 在 *ngFor 绑定到非可迭代(non-iterable)值时抛出的运行时错误,官方名为 "Missing Iterable Differ"。本文以仓库中的错误文档 NG02200.md 为骨架,结合 NgForOf 指令、IterableDiffers 依赖注入机制及单元测试,解释该错误"为什么发生、底层如何抛出、如何修复",并给出迁移到新一代 @for 块(block)的推荐方案。读完你将能准确定位此类循环渲染报错,并写出既高效又类型安全的列表渲染代码。

错误长什么样

该错误的官方文档原文非常简短,只有两个要点:

  1. NgFor 无法为传入的值找到 iterable differ,请确保传入的是可迭代对象,例如 Array
  2. 如果试图遍历对象的键(keys),应改用 KeyValuePipe 而不是 *ngFor

在真实运行时,浏览器控制台里会看到类似下面的完整输出(消息末尾由框架自动拼接错误链接):

ERROR Error: NG02200: Cannot find a differ supporting object 'whaaa' of type 'string'.
NgFor only supports binding to Iterables, such as Arrays.
Find more at https://angular.dev/errors/NG02200

而当你传入的是普通对象时,提示会更"贴心",多出一句建议:

NG02200: Cannot find a differ supporting object '[object Object]' of type 'object'.
NgFor only supports binding to Iterables, such as Arrays.
Did you mean to use the keyvalue pipe?

注意错误码本身:NG02200 对应开发工具包中的负向运行时错误码 NG_FOR_MISSING_DIFFER = -2200,可参见 packages/common/src/errors.ts,对外展示时取绝对值并加 NG 前缀。

根因剖析:一条从模板到 Differ 的调用链

要真正理解这个错误,需要顺着 *ngFor 的展开与变更检测流程看下去。

1. NgForOf 指令在 ngDoCheck 中寻找 differ

模板里的 *ngFor="let item of items" 会被编译成 <ng-template ngFor [ngForOf]="items">,对应实现是 packages/common/src/directives/ng_for_of.ts 中的 NgForOf 指令。核心逻辑集中在 ngDoCheck()

  • 首次拿到非空值时,调用 this._differs.find(value).create(...) 创建该集合专用的 differ 实例(ng_for_of.ts);
  • 之后每次变更检测用该 differ 对集合做 diff(),把新增、删除、移动、身份变化映射为对 DOM 的插入、移除与重排操作。

其中最关键的一行是 this._differs.find(value)——如果没有任何 differ factory 支持传入值的类型,此处就会失败

2. IterableDiffers:可迭代集合的"支持者登记处"

IterableDiffers 定义在 packages/core/src/change_detection/differs/iterable_differs.ts,它是一个"工厂仓库":

find(iterable: any): IterableDifferFactory {
  const factory = this.factories.find((f) => f.supports(iterable));
  if (factory != null) {
    return factory;
  } else {
    throw new RuntimeError(...);   // 没有任何 factory 支持该值
  }
}
  • 默认注册的工厂是 DefaultIterableDifferFactory,其 supports() 通过 isListLikeIterable() 判定传入值是否为类数组可迭代对象(见 packages/core/src/change_detection/differs/default_iterable_differ.ts);
  • 字符串、纯对象、数字等都不被支持,find 抛出底层错误;
  • 如需为自定义类型提供 diffing 策略,可通过 IterableDiffers.extend([...]) 注册新的 IterableDifferFactory(用法注释见 iterable_differs.ts)。

3. 开发模式下"捕获→补全提示→重新抛出"

NgForOf 对 differ 创建过程做了值得注意的加工:生产构建直接调用 find().create();而开发构建(dev mode)则用 try-catch 包住这段逻辑(源码注释明确说明"this logic is duplicated",见 ng_for_of.ts),在 catch 中拼装面向开发者的诊断信息,并针对 typeof value === 'object' 的情况追加 "Did you mean to use the keyvalue pipe?" 提示,最终以 RuntimeError 形式抛出——这就是你看到的 NG02200。

这条逻辑也被单测精确锁定:packages/common/test/directives/ng_for_spec.ts 分别对字符串 'whaaa' 和对象 {'stuff': 'whaaa'} 断言了上述两条不同的报错文案。同一个测试文件中还有一组用例证明:当绑定的值变为 nullundefined*ngFor 不会报错,而是安静地渲染空内容;从非空集合切换到 null 再切回来也都能优雅处理(ng_for_spec.ts)。这是因为 ngDoCheck 只在值存在(truthy)时才去创建 differ。

到底哪些值能过、哪些值会炸

按文档说法,*ngFor 必须收到"某种可迭代类型"。框架层面的类型别名也印证了这一点:

export type NgIterable<T> = Array<T> | Iterable<T>;

NgIterable = Array<T> 或实现了可迭代协议的 Iterable<T>(见 iterable_differs.ts)。实际常见情况归纳如下:

绑定值 结果 说明
Array(数组) ✅ 正常工作 最常见的场景
Set ✅ 正常 每次迭代产出一个元素
Map ✅ 正常 每次迭代产出 [key, value] 二元组,取值需用 item[0]/item[1]
实现了 [Symbol.iterator] 的自定义对象 ✅ 正常 视具体实现
string ❌ NG02200 报 "of type 'string'"
普通对象字面量 {...} ❌ NG02200 报 "of type 'object'",并提示改用 keyvalue pipe
null / undefined ⚠️ 不报错 渲染为空,见上文测试用例
Promise 实例(未 await/async ❌ NG02200 因为 Promise 本身不可迭代

也就是说:普通 JavaScript 对象不是可迭代的。文档给出的正例是 ArraySetMap,而最常见的翻车点正是把一个对象字面量或 Promise 直接塞进了 ngForOf

常见复现场景与修复方法

场景一:组件属性被赋值成了字符串/对象

// bad: 本应是数组,却是字符串或对象
items = 'whaaa';
// items = {a: 1, b: 2};  同样会抛 NG02200
<!-- 抛出 NG02200 -->
<li *ngFor="let item of items">{{ item }}</li>

修复:保证类型是真正的可迭代集合。 若业务数据在对象上,先转换成数组再绑定:

// ok
items = ['whaaa', 'hello', 'angular'];
// 把对象的“值”作为列表
items = Object.values(someRecord);
// 把对象的“键”作为列表
keys = Object.keys(someRecord);

场景二:真的要遍历对象的键/值——改用 KeyValuePipe

文档明确给出的正解是 keyvalue 管道。它把 Object(或 Map)转换成由 {key, value} 项组成的数组,交给 *ngFor 消费,实现见 keyvalue_pipe.tsname: 'keyvalue'),行为测试位于 keyvalue_pipe_spec.ts

<!-- 遍历对象属性,entry.key / entry.value 可用 -->
<div *ngFor="let entry of userProfile | keyvalue">
  <span>{{ entry.key }}:</span> <span>{{ entry.value }}</span>
</div>

场景三:异步数据未落地

使用 async 管道时确保管道后仍是集合,而不是对象:

<!-- ok:async 解析后是数组 -->
<li *ngFor="let user of users$ | async">{{ user.name }}</li>

<!-- 若后端返回 {list: [...]} 这样的对象外壳,需在组件里先做映射 -->
<!-- items = response.list -->

场景四:给长列表加上 trackBy,避免无谓 DOM 重建

*ngFor 默认按对象身份(identity)跟踪元素;数据从服务端重新拉取后即使内容没变,也会整体销毁重建 DOM。NgForOf 提供 ngForTrackBy 输入,通过自定义 TrackByFunction 用业务主键跟踪(源码与使用说明见 ng_for_of.ts):

<li *ngFor="let user of users; trackBy: trackById">{{ user.name }}</li>
trackById(index: number, user: User): number {
  return user.id;
}

此外 NgForOfContext 还导出了 indexcountfirstlastevenodd 等本地变量(见 ng_for_of.ts),可在模板中用 let i = index 等语法取用。

推荐升级:改用 @for

值得一提的是,源码中的 NgForOf 已经标记了弃用信息:@deprecated 20.0,官方建议改用内置的 @for 块(意图在未来的主版本中移除,见 ng_for_of.tsng_for_of.ts 的注释)。因此新代码中处理 NG02200 的"最终方案",是彻底告别 *ngFor

@for (item of items; track item.id) {
  <li>{{ item.name }}</li>
} @empty {
  <li>列表为空时的占位内容</li>
}

要点(详见 控制流指南):

  • @for 语法上要求必须提供 track 表达式,用于在数据变化时最小化 DOM 操作;有唯一标识(如 id/uuid)就用标识,没有稳定标识的静态集合可用 $index,一般不建议直接 track 整个对象引用;
  • *ngFor 不同,@for 优先复用视图:当被 track 的属性变化而对象引用不变时,它只更新绑定(包括组件输入),而不是销毁重建整个元素;
  • keyvalue 管道组合依然成立:@for (entry of userProfile | keyvalue; track entry.key)

继续排查的资源入口

一句话总结:NG02200 是 Angular 在提示你"*ngFor 只认可迭代集合"。先确认绑定值在运行时确实是数组等可迭代对象;若要遍历对象属性,请交给 keyvalue 管道;新项目则直接使用带 track@for 块,从根源上规避这类问题。

登录后查看全文
热门项目推荐
相关项目推荐