首页
/ Angular Query 的 InjectInfiniteQueryOptions 深度解析:用 injector 掌控无限查询的依赖注入上下文

Angular Query 的 InjectInfiniteQueryOptions 深度解析:用 injector 掌控无限查询的依赖注入上下文

2026-09-07 16:50:26作者:钟日瑜

在 TanStack Query 的 Angular 适配层(@tanstack/angular-query-experimental)中,injectInfiniteQuery 用于以声明式方式订阅可无限追加("load more" / "无限滚动")的数据源。而本文主角 InjectInfiniteQueryOptions 正是该函数的第二个可选参数的类型契约——它通过唯一的 injector 属性,决定无限查询应当从哪个 Injector 中创建。读懂它,你就掌握了在非标准注入上下文(如单元测试、手动构建的服务实例)中正确创建无限查询的关键。

概述:这个接口到底长什么样

InjectInfiniteQueryOptionsinjectInfiniteQuery 的第二参数 options 的类型,定义于源码 inject-infinite-query.ts,接口本身极其精简,只有一个可选属性

export interface InjectInfiniteQueryOptions {
  /**
   * The `Injector` in which to create the infinite query.
   *
   * If this is not provided, the current injection context will be used instead (via `inject`).
   */
  injector?: Injector
}

也就是说,它的职责非常单一:允许调用方显式指定"在哪里创建无限查询"。与之配套的入口签名是:

function injectInfiniteQuery<...>(
  injectInfiniteQueryFn: () => CreateInfiniteQueryOptions<...>,
  options?: InjectInfiniteQueryOptions,   // <- 本文主角
): CreateInfiniteQueryResult<...>

函数级文档可对照 injectInfiniteQuery(函数参考) 阅读;查询选项本体 CreateInfiniteQueryOptions 的字段则见 CreateInfiniteQueryOptions 接口

属性详解:injector?

optional injector: Injector;

Injector 是 Angular 依赖注入体系的根类型(@angular/core 导出)。该属性语义如下:

  • 作用:指定"创建这个无限查询"所在的注入器。
  • 默认行为:如果不传该属性,则使用当前注入上下文(inject)获得当前所在的 Injector

典型场景下(在 Angular 组件、指令、管道、服务构造函数等注入上下文内调用),省略 injector 完全没问题,Angular Query 会自动拾取当前注入器,并从中解析出后续所需的依赖。

为什么需要 injector:看看底层实现

要理解这个选项的价值,需要查看 inject-infinite-query.ts 中函数体的完整实现:

export function injectInfiniteQuery(
  injectInfiniteQueryFn: () => CreateInfiniteQueryOptions,
  options?: InjectInfiniteQueryOptions,
) {
  // 1) 未显式提供 injector 时,强制要求处于注入上下文
  !options?.injector && assertInInjectionContext(injectInfiniteQuery)
  // 2) 有 injector 用 injector,没有则从当前注入上下文解析
  const injector = options?.injector ?? inject(Injector)
  // 3) 在目标注入器内执行真正的查询创建逻辑
  return runInInjectionContext(injector, () =>
    createBaseQuery(
      injectInfiniteQueryFn,
      InfiniteQueryObserver as typeof QueryObserver,
    ),
  )
}

第一步:注入上下文守卫

!options?.injector && assertInInjectionContext(injectInfiniteQuery) 是前置校验:

  • 若你没有injector,但又不在组件/指令/服务构造器等注入上下文里,assertInInjectionContext 会抛出 NG0203 错误("inject() 必须在注入上下文中调用"),并且因为传入的是 injectInfiniteQuery 函数本身,报错信息会指明违规调用来自 injectInfiniteQuery,便于定位。
  • 反过来,只要你显式传了 injector,这一守卫就会被短路跳过——这正是测试中"在注入上下文之外也能使用"的实现基础。

第二步与第三步:选定注入器并执行

injector = options?.injector ?? inject(Injector) 采用"显式优先"策略;随后用 Angular 提供的 runInInjectionContext(injector, fn)createBaseQuery(...) 放进指定的注入器执行。createBaseQueryinjectQueryinjectInfiniteQuery 共享的底座,其内部会以 inject(...) 的方式解析一串运行时依赖(见 create-base-query.ts):

  • NgZone:负责把查询状态更新重新调度回 Angular zone;
  • PENDING_TASKS:配合 fetchStatus 维持 Angular 的 pending 任务计数(用于 SSR/水合等待);
  • QueryClient:查询客户端,整个查询缓存与去重机制的核心;
  • injectIsRestoring():判断是否处于持久化恢复状态。

因此,传入不同的 injector,实际上就是换了一套 QueryClient / NgZone 等依赖来源。在多 QueryClient 分层(例如需要局部环境覆盖全局默认客户端)的架构中,这一选项提供了精确的"从哪个环境创建查询"的能力。

什么时候该用 injector(实战场景)

场景一:在注入上下文之外创建无限查询

凡是"没有现成注入上下文、但持有某个注入器实例"的地方,都需要手动传 injector。例如把查询创建逻辑封装进一个由测试框架初始化的 Injector,或配合 Injector.create() 手动搭建迷你环境时。

场景二:单元测试中直接驱动查询

仓库自带测试 inject-infinite-query.test.ts 就专门覆盖了这两类行为:

describe('injection context', () => {
  it('should throw NG0203 with descriptive error outside injection context', () => {
    expect(() => {
      injectInfiniteQuery(() => ({ /* ... */ }))
    }).toThrow(/NG0203(.*?)injectInfiniteQuery/)
  })

  it('should be usable outside injection context when passing an injector', () => {
    const query = injectInfiniteQuery(
      () => ({
        queryKey: key,
        queryFn: ({ pageParam }) =>
          sleep(0).then(() => 'data on page ' + pageParam),
        initialPageParam: 0,
        getNextPageParam: () => 12,
      }),
      {
        injector: TestBed.inject(Injector), // <- 显式指定注入器
      },
    )
    expect(query.status()).toBe('pending')
  })
})

对照组清晰说明了 injector 的边界语义:默认不传会因 NG0203 失败;传入 TestBed.inject(Injector) 后,即便代码并不运行在任何组件上下文中,无限查询也能正常创建并进入 pending 状态。

不传 injector 的常规用法与结果信号

绝大多数业务代码都可以完全忽略第二个参数。下面是在组件中声明式创建无限查询的常规形态(来源参见 Angular Infinite Queries 指南):

import { Component, computed, inject } from '@angular/core'
import { injectInfiniteQuery } from '@tanstack/angular-query-experimental'
import { lastValueFrom } from 'rxjs'
import { ProjectsService } from './projects-service'

@Component({
  selector: 'example',
  templateUrl: './example.component.html',
})
export class Example {
  projectsService = inject(ProjectsService)

  query = injectInfiniteQuery(() => ({
    queryKey: ['projects'],
    queryFn: async ({ pageParam }) => {
      return lastValueFrom(this.projectsService.getProjects(pageParam))
    },
    initialPageParam: 0,
    getPreviousPageParam: (firstPage) => firstPage.previousId ?? undefined,
    getNextPageParam: (lastPage) => lastPage.nextId ?? undefined,
    maxPages: 3,
  }))
}

由于该调用处于组件构造的注入上下文中,injector 会被自动推导为组件所在环境,无需显式传入。返回的查询结果(由 signalProxy 包装)包含一组信号式成员,供模板和逻辑消费,例如 status()data()error()isFetchingNextPage()hasNextPage(),以及 fetchNextPage() / fetchPreviousPage() 方法:

@if (query.isPending()) {
  <p>Loading...</p>
} @else if (query.isError()) {
  <span>Error: {{ query?.error().message }}</span>
} @else {
  @for (page of query?.data().pages; track $index) {
    @for (project of page.data; track project.id) {
      <p>{{ project.name }} {{ project.id }}</p>
    }
  }
}

无限查询选项的核心组成

虽然 InjectInfiniteQueryOptions 只管 injector,无限查询本身的行为选项全部位于传给第一参数的 CreateInfiniteQueryOptions 中,二者不应混淆。核心行为字段如下(更完整的字段见 CreateInfiniteQueryOptions 接口):

字段 作用
queryKey 唯一标识该查询,跨组件共享缓存去重的依据
queryFn({ pageParam }) 按当前页参数拉取一页数据
initialPageParam 第一页的起始页参数,必填
getNextPageParam 根据最后一页推导下一页参数;返回 undefined 表示没有更多页
getPreviousPageParam 向前翻页时推导上一页参数
maxPages 最多保留的页数上限(配合大量追加时裁剪旧页)
initialData 同步初始数据

深入返回类型与重载

injectInfiniteQuery 针对 initialData 的不同形态提供了三组重载,而 InjectInfiniteQueryOptions 在每一组中都作为第二个参数参与签名(见 inject-infinite-query.ts):

重载输入(第一参数返回的类型) 返回类型 语义
DefinedInitialDataInfiniteOptions<...> DefinedCreateInfiniteQueryResult<TData, TError> 提供了非空 initialDatadata 信号被收窄为非空
UndefinedInitialDataInfiniteOptions<...> CreateInfiniteQueryResult<TData, TError> initialData 可能为 undefineddata 可为空
CreateInfiniteQueryOptions<...> CreateInfiniteQueryResult<TData, TError> 通用兜底签名

这些"初始数据有界"类型由 infinite-query-options.ts 提供,它同时导出了 infiniteQueryOptions() 工厂函数,可以把选项以类型安全的方式与 queryKey 的类型标签绑定后复用。泛型参数方面,常见的类型参数默认值包括:TError = DefaultErrorTData = InfiniteData<TQueryFnData>TQueryKey extends QueryKeyTPageParam = unknown——即默认情况下 data() 是一个 InfiniteData(内含 pagespageParams)。

测试如何验证整体行为

除了上述注入上下文专项测试,inject-infinite-query.test.ts 还完整验证了无限查询的核心生命周期:

  • 成功路径:用假定时器推进后,状态从 pending 变为 successpages 依次累积 data on page 0data on page 12,证明 getNextPageParamfetchNextPage() 的追加语义正确(第 30-67 行);
  • 失败路径retry: false 时错误被吸收进状态机,status 变为 errorisError()truefailureCount() 递增为 1第 69-105 行)。

类型层面的编译期约束则由 inject-infinite-query.test-d.tsinfinite-query-options.test-d.ts 保障。

汇总:一张图看懂接口关系

injectInfiniteQuery( optionsFn, options?: InjectInfiniteQueryOptions )
                                        │
                                        └── injector?: Injector
                                             ├── 省略 => 使用当前注入上下文(inject(Injector))
                                             └── 传入 => 在 runInInjectionContext(injector, ...) 中
                                                         执行 createBaseQuery(fn, InfiniteQueryObserver)
                                                                      │
                                                                      └── inject(NgZone / PENDING_TASKS /
                                                                                 QueryClient / isRestoring)

延伸阅读

一句话总结InjectInfiniteQueryOptionsinjectInfiniteQuery 的第二参数,其唯一属性 injector 决定无限查询的依赖注入来源;省略时自动采用当前注入上下文并受 NG0203 守卫保护,显式传入时则允许在测试或非标准上下文中自由创建查询——是理解 Angular Query 响应式注入机制与调试注入错误的关键拼图。

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

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388