在 Angular 项目中,HttpClient 模块为前端提供了发起 HTTP 请求的核心功能。某些场景下,为了实现更快的首屏渲染速度,以及对搜索引擎友好,需要把 Angular 应用运行在 Node.js 服务器端,这就是 Server Side Render 模式。在这种 SSR 模式下,原本只在浏览器端进行的代码与依赖,就要在服务器端执行。这样会遇到一个问题:浏览器端能正常处理的 HttpClient,在服务器端究竟是如何完成请求以及响应的处理过程。有关这个问题的原理解析与示例代码,都可以从下面的内容中找到详细说明。
Angular SSR 和普通浏览器模式的最大区别,是 SSR 运行在 Node.js 环境中,而不是浏览器内核环境。浏览器中存在各种与 DOM 相关的 API,也存在如 XMLHttpRequest 或 fetch 这种 HTTP 请求机制。然而在服务器端,这些浏览器专用的全局 API 并不具备。Angular 之所以能在服务器端正常使用 HttpClient,是因为 Angular 在 SSR 模式里会借助专门的服务端渲染引擎与模块,底层通过服务端可用的方式发起 HTTP 请求。SSR 时采用的核心工具是 Angular Universal,它会让同样的 Angular 组件以及服务,在 Node.js 中得到运行。在构建环节,Angular CLI 会将应用打包出浏览器可执行的 bundle,也会打包出服务端可执行的 bundle。Node.js 启动时,会加载并执行服务端版本的应用逻辑,实现服务端渲染的入口与路由匹配。
HttpClient 在 SSR 场景下的核心工作方式,是替换底层的请求实现为 Node.js 能识别的机制。具体来讲,Angular Universal 会将 HttpClient 请求劫持到服务端可执行的 HTTP 库上,让这一请求走服务器端的网络栈,不再依赖浏览器相关 API。换言之,当 HttpClient 发起一次 get 或者 post 请求时,它在 SSR 环境下相当于在 Node.js 内部进行网络访问。如果与远程服务器交互,那就是从 Node.js 服务器直接发出 HTTP 请求,并得到响应后再进行处理。整个过程中,组件内部的编程方式和在浏览器中是完全一致的,例如依旧可以写 this.http.get(https://api.example.com/data
) 并通过 subscribe 方法获取响应,只不过代码实际运行环境是服务器。
Angular 在编译 SSR 版本时,会生成一个名为 main.server.ts 的入口文件,还有一个 server.ts 文件或类似的脚本文件,用来启动 Node.js 并把 AppServerModule 作为服务端渲染引导模块。在浏览器端的入口则是 main.ts,启动 AppModule 进行浏览器渲染。由于 SSR 需要保证在服务端与客户端分别完成一次渲染,就会存在两次执行某些服务的可能性,包括 HttpClient 在服务器端会先请求数据并注入到页面中,客户端首屏加载完毕后,依旧可能再次发起请求,以实现交互式的前端逻辑。
在 SSR 的执行流程中,Node.js 进程启动后,会创建一个 Express 或其他框架的服务器。Express 负责处理来自浏览器的访问请求,当请求进入时,服务器端会调用 Angular Universal 的渲染引擎,比如 ngExpressEngine。ngExpressEngine 会根据 AppServerModule 生成 Angular SSR 的渲染上下文,把对应的组件树展开和渲染。如果某个组件在其生命周期方法里调用了 HttpClient 的 get 或其他请求方法,那么在服务器端就会立刻通过 Node.js 的 HTTP 模块(或其他底层工具)向目标服务器发起请求,等待数据返回后,整合到组件模板当中。接着 Angular 会将组装好的 HTML 返回给浏览器。浏览器拿到 SSR 返回的 HTML,就能在屏幕上马上渲染出对应内容,提升了首屏加载速度。与此同时,浏览器端的 Angular bundle 也会被下载与执行,以完成后续交互和事件绑定。
整个逻辑说明可以总结如下:
• HttpClient 模块在 SSR 环境下工作时,会把浏览器特有的请求方式切换为服务端的请求方式。
• SSR 环境下,Angular 通过服务器端渲染引擎把组件渲染成字符串,再传给浏览器。
• 同样的 HttpClient 代码,在浏览器与 Node.js 都能执行,但实现细节不同。
• SSR 阶段发送的请求在服务器端执行完毕后,把最终 HTML 直接返回给浏览器。
• 浏览器端加载完后,如果需要再次发起请求,依旧使用 HttpClient,但这时是浏览器环境发出的网络请求。
以下提供一段示例代码,演示如何配置 Angular SSR 并在组件中使用 HttpClient。示例中包含了最关键的 server.ts、main.server.ts、app.server.module.ts 等文件,以及在组件里如何用 HttpClient 发起请求。请注意,为了符合特定格式要求,所有出现的成对英文双引号都已替换为 ` 符号。
示例结构假设目录大致如下:
-
angular.json
-
package.json
-
src
-
main.server.ts
-
app
-
app.server.module.ts
-
app.module.ts
-
app.component.ts
-
-
environments
- environment.ts
-
-
server.ts
以下是 server.ts,用来启动 Node.js 并使用 ngExpressEngine 渲染 Angular 应用:
import `zone.js/dist/zone-node`
import { ngExpressEngine } from `@nguniversal/express-engine`
import { AppServerModule } from `./dist/my-app-server/main`
import * as express from `express`
import { join } from `path`
function app(): express.Express {
const server = express()
const distFolder = join(process.cwd(), `dist/my-app/browser`)
const indexHtml = `index`
server.engine(`html`, ngExpressEngine({
bootstrap: AppServerModule,
}))
server.set(`view engine`, `html`)
server.set(`views`, distFolder)
server.get(`*`, (req, res) => {
res.render(indexHtml, { req })
})
return server
}
function run(): void {
const port = process.env[`PORT`] || 4000
const server = app()
server.listen(port, () => {
console.log(`Node.js server listening on http://localhost:${port}`)
})
}
if (require.main === module) {
run()
}
上面这段代码中,server.engine(html
, ngExpressEngine(…)) 会把 Angular SSR 的核心逻辑接入到 Express 里。当浏览器访问任何路径时,Express 就会调用 SSR 引擎渲染出静态 HTML。
接下来是 main.server.ts 文件,用来导出服务端渲染的根模块。部分代码可能根据项目实际情况有所不同,这里只提供示例内容:
import { enableProdMode } from `@angular/core`
import { environment } from `./environments/environment`
import { AppServerModule } from `./app/app.server.module`
if (environment.production) {
enableProdMode()
}
export { AppServerModule }
这个文件在生产环境下启用 enableProdMode,以提高运行效率。export { AppServerModule } 会告诉 SSR 系统,该模块是服务端的引导模块。
AppServerModule 文件通常命名为 app.server.module.ts,用来声明服务端渲染时要用到的模块:
import { NgModule } from `@angular/core`
import { ServerModule } from `@angular/platform-server`
import { AppModule } from `./app.module`
import { AppComponent } from `./app.component`
@NgModule({
imports: [
AppModule,
ServerModule
],
bootstrap: [ AppComponent ],
})
export class AppServerModule {}
这里直接导入 AppModule 和 ServerModule,然后将 AppComponent 设为引导组件,使得整个 Angular 应用能在服务器端进行初始化并渲染出对应模板。
在 AppModule 中,需要启用 BrowserModule.withServerTransition 并且导入 HttpClientModule,这样才能在 SSR 环境下使用 HttpClient:
import { BrowserModule } from `@angular/platform-browser`
import { NgModule } from `@angular/core`
import { HttpClientModule } from `@angular/common/http`
import { AppComponent } from `./app.component`
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule.withServerTransition({ appId: `my-app` }),
HttpClientModule
],
providers: [],
bootstrap: [ AppComponent ]
})
export class AppModule {}
如果在某个组件中需要使用 HttpClient,依旧可以按照普通浏览器端的写法,例如在 app.component.ts 中示例如下:
import { Component, OnInit } from `@angular/core`
import { HttpClient } from `@angular/common/http`
@Component({
selector: `app-root`,
template: `
<h1>SSR HttpClient Demo</h1>
<div *ngIf="data">
后台返回的数据: {{ data | json }}
</div>
`
})
export class AppComponent implements OnInit {
data: any
constructor(private http: HttpClient) {}
ngOnInit() {
this.http.get(`https://api.example.com/data`)
.subscribe(response => {
this.data = response
console.log(`服务端或浏览器端获取到的数据:`, response)
})
}
}
假设要访问的后端接口地址是 https://api.example.com/data,当 SSR 启动时,如果在服务器端首次渲染阶段执行到这个组件,就会通过 Node.js 的网络能力发起请求,等待返回后将数据注入模板,最后以渲染完毕的 HTML 形式返回给浏览器。等浏览器拿到 HTML 并执行完 Angular 前端逻辑后,可能再次发起相同或者不同请求,也可能把 SSR 阶段生成的内容直接用来展示给用户,避免重复请求。
在 SSR 运行时,最需要注意的是一些仅在浏览器环境才能执行的逻辑,比如直接操作 DOM 或者使用浏览器特有对象 window、document 等。如果这些操作不做检查,就会在服务器端渲染时发生错误。也要当心请求在服务器端和浏览器端可能被重复触发,这需要开发者根据需求做特定处理,比如利用 TransferState 机制或其他缓存方式,避免重复请求带来的性能消耗。
整体而言,在 SSR 模式下使用 HttpClient,是让相同的业务逻辑代码在不同运行时环境(Node.js 和浏览器)里被动态替换为适合对应环境的 HTTP 请求方案。当服务端完成渲染后,会直接将组装好的 HTML 返回给用户,使得应用的首屏加载更快,也让没有执行 JavaScript 的搜索引擎或爬虫,可以拿到完整的页面内容。
这种做法为 SEO 以及性能优化带来诸多好处,也加速了对用户可见的实际加载速度。HttpClient 是 Angular 生态中极为核心且易用的模块,结合 SSR 功能时无需额外更改大量代码,只要正常引入 HttpClientModule,在服务器端构建和启动配置正确,就能无缝地在 Node.js 和浏览器中执行相同的请求逻辑。
在此示例的基础上,还可以进一步拓展 SSR 时的各种场景,比如设置请求头、处理 cookies、重定向、错误处理等,都可以用服务器端的方式来完成。只要时刻注意到 SSR 运行环境没有浏览器特有的对象,就能避免运行时错误。也可以在服务器端使用拦截器,对不同请求进行二次处理。总之,HttpClient 在开启 SSR 后的运行机制,本质是借助 Angular Universal 替换底层实现,让普通的浏览器端请求在服务器端同样可行,并将获取到的数据直接注入到模板里,以返回给浏览器一个预渲染的页面,提升用户体验。
以上就是关于 Angular SSR 场景下 HttpClient 是如何处理 HTTP 请求的原理与示例。虽然涉及多个文件和配置,但核心思想并不复杂,均围绕如何在 Node.js 环境里执行原本浏览器端的代码展开。通过参考上述示例并结合 Angular 官方文档以及实际项目需求,就能成功实现 SSR 部署,并让 HttpClient 在 SSR 中发挥应有的作用。