首页
/ Angular HttpClient 请求指南:从 GET/POST 到高级 Fetch 选项的完整实战解析

Angular HttpClient 请求指南:从 GET/POST 到高级 Fetch 选项的完整实战解析

2026-09-06 18:05:53作者:咎岭娴Homer

HttpClient 是 Angular 官方 @angular/common/http 包提供的 HTTP 客户端服务,本文基于当前仓库中 making-requests.md 指南文档,系统讲解用 HttpClient 发起请求、读取不同类型响应、变更服务器状态、设置 URL 参数与请求头、监听进度事件、处理失败以及使用高级 Fetch 选项的完整方法。读完本文,你将掌握 HttpClient 全部请求选项的语义与取值、Observable 的底层行为特性,以及如何结合服务封装写出健壮可维护的数据访问层。文章还穿插引用仓库中 packages/common/http 下的真实源码作为实现证据。

请求方法与返回的 Observable

HttpClient 提供了与不同 HTTP 动词一一对应的方法,既用于加载数据,也用于在服务器上执行变更(mutation)。每个方法都会返回一个 RxJS Observable。整个 HTTP 客户端的主体实现位于 client.tsHttpClient 类中,每个公开方法(getpostputdeletepatchheadoptions 等)最终都汇聚到统一的 request() 方法构造 HttpRequest

两个需要牢记的核心行为:

  • 订阅才发请求:这些 Observable 是 RxJS 的 "cold" Observable,只有被 .subscribe() 之后,请求才会真正发送到服务器。
  • 订阅 N 次 = 发送 N 次后端请求:由 HttpClient 创建的 Observable 可以被任意次订阅,且每次订阅都会触发一次全新的后端请求,彼此相互独立。

请求方法的最后一个可选参数是一个 options 对象,通过它可以调整请求的各个方面以及返回的响应类型。从源码看,这些选项统一收敛到 request.ts 中定义的 HttpRequestOptions 接口,包括 headersparamsresponseTypeobserve(属于 HttpClientCommonOptions)、reportProgresstimeoutwithCredentialscredentialskeepalivecacheprioritymoderedirectreferrerreferrerPolicyintegritytransferCachecontext 等,本文后续章节会逐一说明其中常用项。

在开始使用前若尚未配置 HTTP 客户端,请先参考 setup.md(通常是在应用配置中使用 provideHttpClient() 注册)。

获取 JSON 数据

从后端加载数据最常见的做法是用 HttpClient.get() 发起 GET 请求。它接收两个参数:端点 URL 字符串,以及一个可选的 options 配置对象。

http.get<Config>('/api/config').subscribe((config) => {
  // process the configuration.
});

注意代码中的泛型类型参数:它声明服务器返回的数据会被当作 Config 类型使用。这个泛型参数是可选的,省略时返回数据的类型为 Object

  • TIP:当数据的结构不确定、且可能包含 undefinednull 时,建议用 unknown 类型而不是 Object 作为响应类型,迫使你在使用前先做类型收窄。
  • CRITICAL(重要):请求方法的泛型只是对服务器返回数据的类型断言HttpClient 并不会校验真实返回的数据是否真的匹配该类型。泛型写错不会在运行时暴露,解析逻辑仍由 responseType 决定。

获取其他类型的数据

默认情况下 HttpClient 假设服务器返回 JSON。当对接非 JSON 接口时,可以通过 responseType 选项告知 HttpClient 期望的响应类型:

responseType 返回的响应类型
'json'(默认) 指定泛型类型的 JSON 数据
'text' 字符串数据
'arraybuffer' 包含原始响应字节的 ArrayBuffer
'blob' Blob 实例

例如把一张 .jpeg 图片的原始字节下载进 ArrayBuffer

http.get('/images/dog.jpg', {responseType: 'arraybuffer'}).subscribe((buffer) => {
  console.log('The image is ' + buffer.byteLength + ' bytes large');
});

responseType 必须是字面量类型:因为 responseType 的值会影响 HttpClient 的返回类型,它必须是字面量类型而不能是宽泛的 string 类型。当传给请求方法的 options 是内联字面量对象时这会自动成立;但如果你把 options 提取到变量或辅助方法中,可能需要显式声明为字面量,例如 responseType: 'text' as const

从源码看,响应解析集中在 fetch.tsparseBody()'json' 会把响应体解码为文本后 JSON.parse(并剥离可能的 XSSI 前缀 )]}',\n),'text' 依据 Content-Type 中的 charset 解码,'blob' 包装成带 content type 的 Blob'arraybuffer' 直接返回原始字节缓冲区。因此,即便服务器返回非 JSON 内容,只要 responseType 设置正确,也能被正确解析。

变更服务器状态

执行变更操作的接口通常要求用 POST 请求携带一个描述新状态或变更内容的请求体。

HttpClient.post()get() 用法类似,只是在 options 之前多接收一个 body 参数:

http.post<Config>('/api/config', newConfig).subscribe((config) => {
  console.log('Updated config:', config);
});

许多不同类型的值都可以作为请求的 bodyHttpClient 会按以下规则序列化:

body 类型 序列化方式
string 纯文本
number、boolean、array 或普通对象 JSON
ArrayBuffer 缓冲区的原始数据
Blob 原始数据,带 Blob 自身的 content type
FormData multipart/form-data 编码数据
HttpParamsURLSearchParams application/x-www-form-urlencoded 格式字符串

上述规则可以直接在 request.tsserializeBody()detectContentTypeHeader() 中找到对应实现:字符串、ArrayBufferBlobFormDataURLSearchParams 原样透传;HttpParams 实例调用其 toString();对象、布尔值、数组走 JSON.stringify。同时,若未显式设置 Content-TypedetectContentTypeHeader() 会自动推断:字符串为 text/plainHttpParamsapplication/x-www-form-urlencoded;charset=UTF-8、数组/对象/数值/布尔为 application/jsonBlob 使用其自身的 type,而 FormDataArrayBuffer 交由底层自动处理(fetch.ts 在构造 fetch 参数时调用该方法)。

IMPORTANT:请务必记得对变更类请求的 Observable 执行 .subscribe(),否则请求根本不会真正发出。

设置 URL 参数

使用 params 选项指定应包含在请求 URL 中的查询参数。

最简单的做法是传入一个对象字面量:

http
  .get('/api/config', {
    params: {filter: 'all'},
  })
  .subscribe((config) => {
    // ...
  });

如果需要对参数的构造或序列化做更精细的控制,可以传入一个 HttpParams 实例。

IMPORTANTHttpParams 实例是**不可变(immutable)**的,无法直接修改。诸如 append() 之类的变更方法会返回一个应用了变更的新实例。

const baseParams = new HttpParams().set('filter', 'all');

http
  .get('/api/config', {
    params: baseParams.set('details', 'enabled'),
  })
  .subscribe((config) => {
    // ...
  });

HttpParams 在构造时支持 HttpParamsOptions 中的几个可选配置:fromString(直接以查询字符串格式解析)、fromObject(以对象映射构建)、encoder(自定义编解码器)。若同时传入 fromStringfromObject,构造器会直接抛出运行时错误(见 params.ts)。

另一个值得留意的细节是 URL 拼接逻辑:HttpRequest 构造函数在把序列化后的参数拼接到 URL 时会先剥离 # 之后的 fragment,把查询串插入到 fragment 之前,避免参数被追加到 fragment 后面、只在浏览器端可见从而绕过服务端安全校验的情况。

HttpParams 还可以搭配自定义 HttpParameterCodec,决定 HttpClient 如何把参数编码进 URL。

自定义参数编码

默认情况下,HttpParams 使用内置的 HttpUrlEncodingCodec 来编码/解码参数键与值。这个默认 codec 的实现逻辑位于 params.ts:先对键值执行 encodeURIComponent,随后再把 @:$,;=?/ 这八个字符还原为字面量(这些字符在 URL 查询串中通常可以安全保留)。

你也可以提供自己的 HttpParameterCodec 实现来完全自定义编码与解码方式:

import {HttpClient, HttpParams, HttpParameterCodec} from '@angular/common/http';
import {inject} from '@angular/core';

export class CustomHttpParamEncoder implements HttpParameterCodec {
  encodeKey(key: string): string {
    return encodeURIComponent(key);
  }

  encodeValue(value: string): string {
    return encodeURIComponent(value);
  }

  decodeKey(key: string): string {
    return decodeURIComponent(key);
  }

  decodeValue(value: string): string {
    return decodeURIComponent(value);
  }
}

export class ApiService {
  private http = inject(HttpClient);

  search() {
    const params = new HttpParams({
      encoder: new CustomHttpParamEncoder(),
    })
      .set('email', 'dev+alerts@example.com')
      .set('q', 'a & b? c/d = e');

    return this.http.get('/api/items', {params});
  }
}

HttpParameterCodec 接口本身定义于 params.ts,要求实现 encodeKeyencodeValuedecodeKeydecodeValue 四个方法。

设置请求头

使用 headers 选项指定请求中应包含的请求头。同样可以传对象字面量:

http
  .get('/api/config', {
    headers: {
      'X-Debug-Level': 'verbose',
    },
  })
  .subscribe((config) => {
    // ...
  });

需要更精细地构造请求头时,可以传入 HttpHeaders 实例。

IMPORTANT:与 HttpParams 一样,HttpHeaders 实例也是不可变的。诸如 append() 之类的变更方法会返回新的 HttpHeaders 实例。

const baseHeaders = new HttpHeaders().set('X-Debug-Level', 'minimal');

http
  .get<Config>('/api/config', {
    headers: baseHeaders.set('X-Debug-Level', 'verbose'),
  })
  .subscribe((config) => {
    // ...
  });

headers.ts 源码可以看到,HttpHeaders 底层对 header 名称做了规范化与小写化存储(has/get 时大小写不敏感),并提供了 hasgetgetAllappendsetdelete 等方法,其中 append/set/delete 都返回新实例以维持不可变性。

与服务器响应对象交互

为方便起见,HttpClient 默认返回的 Observable 直接产出服务器返回的数据(即响应体)。但有时我们需要查看完整的响应,例如读取某些特定的响应头。

此时把 observe 选项设为 'response'

http.get<Config>('/api/config', {observe: 'response'}).subscribe((res) => {
  console.log('Response status:', res.status);
  console.log('Body:', res.body);
});

HttpClient 的方法重载可以看出(client.ts),observe 有三个合法取值:'body'(默认,返回响应体)、'response'(返回完整 HttpResponse)、'events'(返回事件流)。HttpResponseHttpHeaderResponseHttpErrorResponse 共同继承自 HttpResponseBase(定义见 response.ts),包含 statusstatusTextheadersurl 等字段。

observe 必须是字面量类型:由于 observe 的值同样决定 HttpClient 的返回类型,它必须是字面量而不能是宽泛的 string 类型。options 内联在方法调用处时自动成立;若提取到变量或辅助方法中,需要显式写成 observe: 'response' as const

接收原始进度事件

除了响应体或响应对象,HttpClient 还能返回一条对应请求生命周期特定时刻的原始事件流。这些事件包括:请求被发出、收到响应头、响应体接收完毕等时刻;当请求或响应体较大时,还可能包含报告上传/下载状态的进度事件(progress events)

进度事件默认是关闭的(因为会产生性能开销),可通过 reportProgress 选项开启。若同时需要区分上传/下载,仓库较新版本还引入了更细粒度的 reportUploadProgressreportDownloadProgress 选项(见 request.ts)。

NOTEHttpClient 默认的 fetch 后端不报告上传进度事件。如果你的应用需要上传进度事件,请在 provideHttpClient(...) 配置中使用 withXhr()(XHR 后端)。对应的运行时限制可在 fetch.ts 中看到:当请求设置了 reportUploadProgress 时,FetchBackend 会直接抛出提示改用 withXhr() 的运行时错误。withFetch / withXhr 两个特性的定义位于 provider.ts

要观察事件流,把 observe 设为 'events'

http
  .post('/api/upload', myData, {
    reportProgress: true,
    observe: 'events',
  })
  .subscribe((event) => {
    switch (event.type) {
      case HttpEventType.UploadProgress:
        console.log('Uploaded ' + event.loaded + ' out of ' + event.total + ' bytes');
        break;
      case HttpEventType.Response:
        console.log('Finished uploading!');
        break;
    }
  });

observe 必须是字面量类型:同样地,若把 options 提取到变量中,请使用 observe: 'events' as const

事件流中报告的每个 HttpEvent 都带有一个 type 字段,用于区分事件含义(HttpEventType 枚举定义于 response.ts):

type 事件含义
HttpEventType.Sent 请求已派发给服务器
HttpEventType.UploadProgress HttpUploadProgressEvent,报告上传请求体的进度
HttpEventType.ResponseHeader 已收到响应头部,包含状态码与响应头
HttpEventType.DownloadProgress HttpDownloadProgressEvent,报告下载响应体的进度
HttpEventType.Response 已收到完整响应,包括响应体
HttpEventType.User 来自 HTTP 拦截器(interceptor)的自定义事件

其中进度事件类型 HttpProgressEventHttpDownloadProgressEventHttpUploadProgressEvent 的定义同样位于 response.ts。关于利用 HttpEventType.User 与拦截器协作的更多内容,可参考 interceptors.md

处理请求失败

HTTP 请求可能以三种方式失败:

  • 网络或连接错误:请求无法到达后端服务器;
  • 请求超时:设置了 timeout 选项但请求未在限定时间内完成;
  • 后端错误响应:服务器收到了请求但处理失败,返回了错误状态码。

HttpClient 会把上述所有错误统一封装成 HttpErrorResponse,并通过 Observable 的错误通道(error channel)抛给订阅者。其中:

  • 网络错误与超时错误的 status0error 是一个 ProgressEvent 实例;
  • 后端错误则带有后端返回的失败 status 码,error 为错误响应体。

可以检查该响应来判断错误成因并决定相应的处理动作。对应底层逻辑见 fetch.tsstatus 在 2xx 区间内走成功通道发出 HttpResponsecomplete(),否则通过 observer.error(new HttpErrorResponse(...)) 走错误通道;当用户请求 JSON 但响应体解析失败时,成功状态也可能转为错误。

RxJS 库提供了多个对错误处理有用的操作符:

  • catchError:把错误响应转换为可供 UI 展示的值,例如让 UI 显示错误页或占位值,必要时捕获错误原因。
  • retry 系列操作符:像网络中断这类瞬时错误,直接重试请求往往就能成功。例如 retry() 操作符会自动重订阅失败的 Observable 指定的次数。

请求超时

可以为请求设置 timeout 选项(单位毫秒),与其它请求选项一并传入。若后端请求在限定时间内未完成,请求将被中止并抛出一个错误:

http
  .get('/api/config', {
    timeout: 3000,
  })
  .subscribe({
    next: (config) => {
      console.log('Config fetched successfully:', config);
    },
    error: (err) => {
      // If the request times out, an error will have been emitted.
    },
  });

NOTEtimeout 只作用于后端 HTTP 请求本身,而不是整条请求处理链路的超时。因此拦截器引入的任何延迟都不会影响该选项。

实现层面有两个细节值得了解(request.ts):timeout 必须是正整数,小于 1 或非整数会在构造请求时抛出 RuntimeError;超时计时通过 AbortController 实现,且运行在 Angular Zone 之外以避免多余的变更检测(fetch.ts)。另外,HttpClient 订阅被取消时也会调用 aborter.abort() 中止进行中的请求(fetch.ts)。

高级 Fetch 选项

当使用 fetch 后端(默认后端)时,HttpClient 支持一系列对标浏览器 Fetch API 的高级选项,可用于提升性能与用户体验。这些选项最终在 fetch.tscreateRequestInit() 中逐一映射到底层 fetch()RequestInit。下文逐一说明其语义与适用场景。

Keep-alive 长连接

keepalive 选项允许请求的生命周期超出发起它的页面——即使页面已被卸载,请求仍可继续完成。这对于埋点统计或日志上报类请求尤其有用,它们需要在用户导航离开页面后仍然跑完:

http
  .post('/api/analytics', analyticsData, {
    keepalive: true,
  })
  .subscribe();

HTTP 缓存控制

cache 选项控制请求与浏览器 HTTP 缓存的交互方式,对重复请求的性能有显著影响:

// 直接使用缓存响应,无论是否新鲜
http
  .get('/api/config', {
    cache: 'force-cache',
  })
  .subscribe((config) => {
    // ...
  });

// 总是从网络获取,绕过缓存
http
  .get('/api/live-data', {
    cache: 'no-cache',
  })
  .subscribe((data) => {
    // ...
  });

// 只使用缓存响应,缓存中不存在则失败
http
  .get('/api/static-data', {
    cache: 'only-if-cached',
  })
  .subscribe((data) => {
    // ...
  });

面向 Core Web Vitals 的请求优先级

priority 选项用于声明请求的相对重要程度,帮助浏览器优化资源加载顺序,从而改善 Core Web Vitals 指标:

// 关键资源使用高优先级
http
  .get('/api/user-profile', {
    priority: 'high',
  })
  .subscribe((profile) => {
    // ...
  });

// 非关键资源使用低优先级
http
  .get('/api/recommendations', {
    priority: 'low',
  })
  .subscribe((recommendations) => {
    // ...
  });

// auto(默认)交给浏览器决定
http
  .get('/api/settings', {
    priority: 'auto',
  })
  .subscribe((settings) => {
    // ...
  });

可用的 priority 取值:

  • 'high':高优先级,尽早加载(例如关键用户数据、首屏以上内容);
  • 'low':低优先级,资源可用时再加载(例如埋点、预取数据);
  • 'auto':由浏览器根据请求上下文决定优先级(默认值)。

TIP:对影响 Largest Contentful Paint(LCP)的请求使用 priority: 'high',对不影响首屏体验的请求使用 priority: 'low'

请求模式(跨域策略)

mode 选项控制请求如何处理跨域,并决定响应类型:

// 仅允许同源请求
http
  .get('/api/local-data', {
    mode: 'same-origin',
  })
  .subscribe((data) => {
    // ...
  });

// 允许启用 CORS 的跨域请求
http
  .get('https://api.external.com/data', {
    mode: 'cors',
  })
  .subscribe((data) => {
    // ...
  });

// 无 CORS 的简单跨域请求
http
  .get('https://external-api.com/public-data', {
    mode: 'no-cors',
  })
  .subscribe((data) => {
    // ...
  });

可用的 mode 取值:

  • 'same-origin':只允许同源请求,跨域请求直接失败;
  • 'cors':允许带 CORS 的跨域请求(默认);
  • 'no-cors':允许不带 CORS 的简单跨域请求,但响应是 opaque(不透明)的。

TIP:在浏览器中,对绝不应跨域的敏感请求使用 mode: 'same-origin'

IMPORTANT(SSR 注意事项):在 Node.js 上执行 SSR 时,HttpClient 使用的是基于 Undici 的 Node.js fetch 实现,而 Undici 不会执行浏览器式的 CORS 检查,因此 mode: 'same-origin' 并不能限制服务端请求。若有用户可影响的 URL,务必通过白名单(allowlist)校验。

重定向处理

redirect 选项指定如何处理服务器返回的重定向响应:

// 自动跟随重定向(默认行为)
http
  .get('/api/resource', {
    redirect: 'follow',
  })
  .subscribe((data) => {
    // ...
  });

// 阻止自动重定向
http
  .get('/api/resource', {
    redirect: 'manual',
  })
  .subscribe((response) => {
    // 手动处理重定向
  });

// 把重定向视为错误
http
  .get('/api/resource', {
    redirect: 'error',
  })
  .subscribe({
    next: (data) => {
      // 成功响应
    },
    error: (err) => {
      // 重定向响应会触发此错误处理
    },
  });

可用的 redirect 取值:

  • 'follow':自动跟随重定向(默认);
  • 'error':把重定向视为错误;
  • 'manual':不自动跟随重定向,返回重定向响应。

TIP:需要自定义重定向逻辑时使用 redirect: 'manual'

凭据(Credentials)处理

credentials 选项控制跨域请求是否携带 cookie、授权头等凭据,这对认证场景尤为重要:

// 跨域请求携带凭据
http
  .get('https://api.example.com/protected-data', {
    credentials: 'include',
  })
  .subscribe((data) => {
    // ...
  });

// 绝不发送凭据(跨域的默认值)
http
  .get('https://api.example.com/public-data', {
    credentials: 'omit',
  })
  .subscribe((data) => {
    // ...
  });

// 仅同源请求发送凭据
http
  .get('/api/user-data', {
    credentials: 'same-origin',
  })
  .subscribe((data) => {
    // ...
  });

// withCredentials 会覆盖 credentials 设置
http
  .get('https://api.example.com/data', {
    credentials: 'omit', // 这一行将被忽略
    withCredentials: true, // 这会强制 credentials: 'include'
  })
  .subscribe((data) => {
    // 请求仍会携带凭据
  });

// 传统写法(仍受支持)
http
  .get('https://api.example.com/data', {
    withCredentials: true,
  })
  .subscribe((data) => {
    // 等价于 credentials: 'include'
  });

IMPORTANTwithCredentials 的优先级高于 credentials。当两者同时指定时,withCredentials: true 始终产生 credentials: 'include' 的效果,无论显式传入的 credentials 值是什么。源码 fetch.ts 在构造请求参数时正是这样处理:先取 req.credentials,随后若 withCredentials 为真则强制改写为 'include' 并输出一条开发警告。为了避免混淆,请勿混用这两个选项。

可用的 credentials 取值:

  • 'omit':绝不发送凭据;
  • 'same-origin':仅同源请求发送凭据(默认);
  • 'include':总是发送凭据,包括跨域请求。

TIP:当需要向支持 CORS 的不同域发送认证 cookie 或请求头时使用 credentials: 'include'

IMPORTANT(SSR 注意事项):SSR 期间 credentials: 'include' 不会自动转发来自浏览器请求的 cookie。credentials 选项也不会移除你显式添加的 CookieAuthorization 请求头;而 Undici 允许发送一些浏览器禁止的请求头。因此只应把凭据类请求头转发给可信来源(trusted origins)。

Referrer

referrer 选项用于控制请求携带的 referrer 信息,涉及隐私与安全考量:

// 发送特定的 referrer URL
http
  .get('/api/data', {
    referrer: 'https://example.com/page',
  })
  .subscribe((data) => {
    // ...
  });

// 使用当前页面作为 referrer(默认行为)
http
  .get('/api/analytics', {
    referrer: 'about:client',
  })
  .subscribe((data) => {
    // ...
  });

referrer 选项可接受的值:

  • 合法的 URL 字符串:设置要发送的具体 referrer URL;
  • 空字符串 '':不发送任何 referrer 信息;
  • 'about:client':使用默认 referrer(当前页面 URL)。

TIP:对于不想泄露来源页面 URL 的敏感请求,使用 referrer: ''

Referrer Policy

referrerPolicy 选项控制随请求发送多少 referrer 信息(即发起请求的页面 URL),同时影响隐私保护与分析统计,用于在数据可见性与安全性之间做权衡:

// 无论当前页面如何都不发送 referrer 信息
http
  .get('/api/data', {
    referrerPolicy: 'no-referrer',
  })
  .subscribe();

// 只发送 origin(例如 https://example.com)
http
  .get('/api/analytics', {
    referrerPolicy: 'origin',
  })
  .subscribe();

referrerPolicy 选项可接受的值:

  • 'no-referrer':绝不发送 Referer 请求头。
  • 'no-referrer-when-downgrade':同源请求以及安全源之间的请求(HTTPS→HTTPS)发送 referrer;从安全源到较低安全源(HTTPS→HTTP)时省略。
  • 'origin':只发送 referrer 的 origin(协议、主机、端口),省略路径与查询串。
  • 'origin-when-cross-origin':同源请求发送完整 URL,跨域请求只发送 origin。
  • 'same-origin':同源请求发送完整 URL,跨域请求不发送 referrer。
  • 'strict-origin':仅当协议安全级别未降级时(例如 HTTPS→HTTPS)才只发送 origin;发生降级时省略 referrer。
  • 'strict-origin-when-cross-origin':浏览器默认行为。同源请求发送完整 URL,未降级的跨域请求发送 origin,发生降级时省略 referrer。
  • 'unsafe-url':总是发送完整 URL(含路径与查询串)。这可能泄露敏感数据,请谨慎使用。

TIP:对隐私敏感的请求,优先选择 'no-referrer''origin''strict-origin-when-cross-origin' 这类保守取值。

完整性校验(Integrity)

integrity 选项通过提供响应内容的加密哈希值来校验响应未被篡改,在从 CDN 加载脚本等资源时尤其有用:

// 使用 SHA-256 哈希校验响应完整性
http
  .get('/api/script.js', {
    integrity: 'sha256-ABC123...',
    responseType: 'text',
  })
  .subscribe((script) => {
    // 脚本内容已按哈希完成校验
  });

IMPORTANTintegrity 要求响应内容与提供的哈希精确匹配。内容不匹配时请求将以网络错误失败。 TIP:从外部来源加载关键资源时使用 Subresource Integrity(SRI)确保其未被修改,哈希可用 openssl 等工具生成。

HTTP Observable 的底层行为

HttpClient 的每个请求方法都会构造并返回一个目标响应类型的 Observable。理解这些 Observable 的工作方式对正确使用 HttpClient 至关重要:

  • Cold Observable(冷可观察对象)HttpClient 产生的是 RxJS 所谓的 cold Observable——在订阅之前不会真正发起任何请求,只有订阅后才把请求派发到服务器。对同一个 Observable 多次订阅会触发多次后端请求,且每次订阅相互独立。

TIP:可以把 HttpClientObservable 视作真实服务器请求的"蓝图(blueprints)"。

  • 取消订阅即中止请求:一旦订阅,取消订阅会中止进行中的请求。若该 Observable 经由 async 管道订阅,当用户导航离开当前页面时请求会被自动取消;若配合 switchMap 这类组合操作符,取消还会清理掉陈旧的请求。

  • 自动完成:响应返回后,来自 HttpClientObservable 通常会完成(complete,尽管拦截器可能影响这一点)。由于这种自动完成,即使不清理 HttpClient 订阅通常也没有内存泄漏风险。不过,与任何异步操作一样,强烈建议在组件销毁时清理组件内使用的订阅——否则订阅回调仍可能运行,并在试图与已销毁的组件交互时遭遇错误。

TIP:使用 async 管道或 toSignal 操作符来订阅 Observable,可以确保订阅被妥善释放。

最佳实践:服务封装 + 响应式模板

虽然 HttpClient 可以直接注入并在组件中使用,官方仍建议创建可复用的、可注入的服务来隔离并封装数据访问逻辑。例如这个 UserService 封装了按 id 请求用户数据的逻辑:

@Service()
export class UserService {
  private http = inject(HttpClient);

  getUser(id: string): Observable<User> {
    return this.http.get<User>(`/api/user/${id}`);
  }
}

在组件中,可以结合 @ifasync 管道,让 UI 在数据加载完成后才渲染:

import {AsyncPipe} from '@angular/common';

@Component({
  imports: [AsyncPipe],
  template: `
    @if (user$ | async; as user) {
      <p>Name: {{ user.name }}</p>
      <p>Biography: {{ user.biography }}</p>
    }
  `,
})
export class UserProfile {
  userId = input.required<string>();
  user$!: Observable<User>;

  private userService = inject(UserService);

  constructor(): void {
    effect(() => {
      this.user$ = this.userService.getUser(this.userId());
    });
  }
}

这里把请求职责收敛到服务层、把订阅职责交给 async 管道,既便于复用与测试(可参考 testing.mdHttpClient 的测试手法),又能利用上一节所述的取消订阅机制自动中止请求。

小结

HttpClient 的请求 API 围绕一套统一的 options 模型展开:responseType 决定响应解析方式,observe 决定观察粒度(响应体/完整响应/事件流),params/headers 以不可变对象管理 URL 参数与请求头,timeout 与 RxJS 错误处理操作符配合应对失败场景,而 fetch 后端专属的 keepalivecacheprioritymoderedirectcredentialsreferrerreferrerPolicyintegrity 等选项则提供了接近原生 Fetch API 的精细化控制。对各项选项语义与底层实现的完整把握,参见 making-requests.md 原文,以及 overview.mdinterceptors.md 等 HTTP 指南系列文档;若希望了解源码级细节,可继续阅读 request.tsparams.tsheaders.tsclient.tsfetch.ts

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