☰
Angular 如何用 httpResource 进行响应式数据请求并自动取消过期请求?
2026/10/19 9:36:23 网站建设 项目流程

Angular 如何用 httpResource 进行响应式数据请求并自动取消过期请求?

【免费下载链接】angularDeliver web apps with confidence 🚀项目地址: https://gitcode.com/GitHub_Trending/an/angular

在 Angular 应用中,常见的需求是:根据某个响应式输入(例如当前选中的userId)向后端拉取数据,输入一变就自动重新请求,而且不能出现旧请求晚到、覆盖新数据的竞态。httpResource就是为这个场景设计的:它是构建在HttpClient之上的响应式请求 API,把请求状态和响应数据暴露为 signal 集合,依赖的 signal 变化时自动发起新请求,并在新请求发出前取消尚未完成的旧请求。

该 API 从 Angular 22.0 起公开(源码中标注@publicApi 22.0,见 resource.ts 与 resource_api.ts)。版本前提上,HttpClient 配置指南 说明HttpClient在 Angular v21 及以后默认可注入,因此 Angular 22 的应用不需要额外配置就能使用httpResource。

准备条件:确认 HttpClient 可用

httpResource使用全局的HttpClient实例发请求。官方指南(http-resource.md)给出的建议是:

  • 不需要 HTTP 特性配置时,直接调用httpResource即可,无需provideHttpClient(...);
  • 只有需要配置拦截器(interceptors)或 XSRF 选项等 HTTP 特性时,才在app.config.ts的providers中加入provideHttpClient(...):
export const appConfig: ApplicationConfig = { providers: [provideHttpClient(/* add features here, such as withInterceptors(...) */)], };

由于httpResource底层走HttpClient,你在provideHttpClient里配置的拦截器对其同样生效。

定义响应式数据请求

最简单的形式是返回一个 url 的函数,把响应式依赖放进参数里:

userId = input.required<string>(); user = httpResource(() => `/api/user/${userId()}`); // A reactive function as argument

这里有两个和HttpClient直接使用时不同的关键点:

  1. 响应式触发:httpResource是响应式的,凡是它依赖的 signal(如userId)变化,资源都会发出一个新的 HTTP 请求;
  2. 立即发起请求:httpResource发起请求是 eager 的(资源创建时即开始请求),而HttpClient只在订阅返回的Observable时才发起请求。

过期请求是如何被取消的

官方指南的原文行为说明是:每当依赖的 signal 变化,资源会发出新的 HTTP 请求;如果此时已有请求处于 pending 状态,资源会先取消未完成的请求,再发出新请求(If a request is already pending, the resource cancels the outstanding request before issuing a new one)。

从实现上可以核对这一机制:在 resource.ts 中,HttpClient的订阅被包装在资源的执行函数内,并为每次请求注册了一个abortSignal监听器——当信号触发中止时,onAbort会调用sub?.unsubscribe()取消该次请求的订阅。也就是说,依赖变化导致的旧请求会被资源系统整体放弃,不会继续把结果写回,避免了过期响应覆盖新数据的情况。

在模板中展示请求状态

httpResource返回的资源上带有状态 signal,可以直接用于模板控制分支(以下示例来自官方指南):

@if (user.hasValue()) { <user-details [user]="user.value()" /> } @else if (user.error()) { <div>Could not load user information</div> } @else if (user.isLoading()) { <div>Loading user info...</div> }

指南特别指出:在一个处于 error 状态的 resource 上读取valuesignal 会在运行时抛错,因此建议用hasValue()守护value的读取。上面的@if (user.hasValue())分支写法即遵循此建议。

进阶:请求对象与自定义响应类型

需要传 header、query 参数或 fetch 选项时,可以传入与HttpClient请求对象相似的结构,其中需要响应式的属性用 signal 组合:

user = httpResource(() => ({ url: `/api/user/${userId()}`, method: 'GET', headers: { 'X-Special': 'true', }, params: { 'fast': 'yes', }, reportProgress: true, transferCache: true, }));

官方示例还演示了priority、mode、redirect、cache、credentials、referrer、integrity等更多 fetch 选项,完整字段定义见 HttpResourceRequest 接口。method缺省为GET;reportProgress: true时,进度事件会通过资源的progresssignal 投递。

默认情况下响应按 JSON 解析。需要其他返回类型时,使用httpResource的子函数,value()的类型随之改变:

httpResource.text(() => ({ … })); // returns a string in value() httpResource.blob(() => ({ … })); // returns a Blob object in value() httpResource.arrayBuffer(() => ({ … })); // returns an ArrayBuffer in value()

可选分支:用 parse 校验响应

如果要用 Zod、Valibot 等库校验后端响应,可以通过parse选项接入,parse函数的返回类型即资源value的类型。官方示例以 StarWars API 为例:

const starWarsPersonSchema = z.object({ name: z.string(), height: z.number({coerce: true}), edited: z.string().datetime(), films: z.array(z.string()), }); export class CharacterViewer { id = signal(1); swPersonResource = httpResource(() => `https://swapi.info/api/people/${this.id()}`, { parse: starWarsPersonSchema.parse, }); }

校验失败时,错误会被封装为资源错误进入error状态,而不是让应用崩溃。

验证:用 HttpTestingController 测试 httpResource

因为httpResource是HttpClient的包装,可以用与HttpClient完全相同的测试 API(Test requests)。配置TestBed时加入provideHttpClientTesting(),请求会打到测试后端;需要自定义拦截器等特性时,provideHttpClient(...)必须放在provideHttpClientTesting()之前。

官方指南给出的httpResource单元测试示例(其中TestBed.tick()用于触发资源 effect,whenStable()确保值已传播):

TestBed.configureTestingModule({ providers: [provideHttpClientTesting()], }); const id = signal(0); const mockBackend = TestBed.inject(HttpTestingController); const response = httpResource(() => `/data/${id()}`, {injector: TestBed.inject(Injector)}); TestBed.tick(); // Triggers the effect const firstRequest = mockBackend.expectOne('/data/0'); firstRequest.flush(0); // Ensures the values are propagated to the httpResource await TestBed.inject(ApplicationRef).whenStable(); expect(response.value()).toEqual(0);

这个示例展示了完整的验证路径:expectOne('/data/0')断言依赖当前 signal 值发出的请求确实发生,flush(0)模拟后端响应,最后response.value()的断言确认响应被解析并写入了资源。测试文档还建议用mockBackend.verify()断言没有发出预期之外的请求,可以放进afterEach()。要模拟后端错误,可用req.flush('Failed!', {status: 500, statusText: 'Internal Server Error'})触发资源的 error 分支并断言error()状态。

使用限制

  • 不要用于变更类请求:官方指南明确建议,POST、PUT这类 mutation 不要用httpResource,直接调用底层HttpClientAPI。
  • 读取value前先用hasValue()守护:error 状态下直接读value会抛运行时错误。
  • 请求对象中的 url 不应包含 query 参数,query 参数通过params字段指定(见 HttpResourceRequest 的字段说明)。

完成上述步骤后,你可以在测试中通过expectOne+flush+expect(response.value())这条链路确认请求按预期发出、过期请求被取消、响应正确落到 signal 上;模板侧则通过hasValue()/error()/isLoading()三个分支确认页面状态机与之一致。

【免费下载链接】angularDeliver web apps with confidence 🚀项目地址: https://gitcode.com/GitHub_Trending/an/angular

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询