如何从NestJS Interceptor向hbs模板生成的DOM元素添加新参数?
问题描述
刚开始学习NestJS,尝试通过hbs模板将Interceptor中添加的响应时间变量渲染到页面,但页面上的{{time}}字段始终为空。具体情况如下:
现有代码
LoggingInterceptor 实现
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable, map } from 'rxjs'; export interface Response<T> { data: T; } @Injectable() export class LoggingInterceptor<T> implements NestInterceptor<T, Response<T>> { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { const start = Date.now(); return next .handle() .pipe( map((data) => ({...data, time: Date.now() - start})), ); } }
HBS模板片段
<div> <h3>Contacts</h3> <p>Some links</p> <p>Total time server: {{time}}</p> </div>
测试情况
- 当Controller方法返回JSON对象时,Interceptor能正常给响应添加
time字段:
@UseInterceptors(LoggingInterceptor) @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get('/test') doSmth() { return {smt: 'aaa', title: 'test'}; } }
- 但使用
@Res()手动调用res.render()渲染hbs模板时,Interceptor完全失效:
@UseInterceptors(LoggingInterceptor) @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get('/') root(@Res() res: Response<any>) { return res.render( 'home', { layout: 'layout/layout_main', message: 'Hello world!' }, ); } }
解决方案
问题根源
NestJS的Interceptor只对Controller方法返回的Observable/值生效。当你使用@Res()直接操作响应对象并调用res.render()时,Controller方法实际上没有返回可供Interceptor处理的数据——res.render()直接向客户端发送了响应,Interceptor无法拦截到这个过程,自然无法添加time变量。
解决方法
方法一:使用NestJS的Render装饰器(推荐)
放弃手动调用res.render(),改用NestJS提供的@Render()装饰器让框架自动处理模板渲染,这样Interceptor就能正常拦截并修改渲染参数:
import { Controller, Get, Render, UseInterceptors } from '@nestjs/common'; import { LoggingInterceptor } from './logging.interceptor'; @UseInterceptors(LoggingInterceptor) @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get('/') @Render('home') // 指定要渲染的模板名称 root() { // 返回的对象会作为模板参数,Interceptor会在这里添加time字段 return { layout: 'layout/layout_main', message: 'Hello world!' }; } }
方法二:手动在Interceptor中传递响应时间(适合必须用@Res()的场景)
如果必须使用@Res(),可以通过ExecutionContext获取请求对象,在Interceptor中挂载响应时间,再在Controller中传入模板:
- 修改Interceptor:
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable, tap } from 'rxjs'; @Injectable() export class LoggingInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { const start = Date.now(); const request = context.switchToHttp().getRequest(); return next .handle() .pipe( tap(() => { // 计算响应时间并挂载到request对象上 request.responseTime = Date.now() - start; }), ); } }
- 修改Controller:
import { Controller, Get, Res, Req, UseInterceptors } from '@nestjs/common'; import { Response, Request } from 'express'; import { LoggingInterceptor } from './logging.interceptor'; @UseInterceptors(LoggingInterceptor) @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get('/') root(@Res() res: Response, @Req() req: Request) { return res.render( 'home', { layout: 'layout/layout_main', message: 'Hello world!', time: req.responseTime // 从request中取出响应时间传入模板 }, ); } }
方法三:使用res.locals全局传递变量(进阶)
通过Express的res.locals传递变量,所有模板可直接访问,无需手动传入:
- 修改Interceptor:
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable, tap } from 'rxjs'; @Injectable() export class LoggingInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { const start = Date.now(); const response = context.switchToHttp().getResponse(); return next .handle() .pipe( tap(() => { // 将响应时间存入res.locals,模板可直接访问{{time}} response.locals.time = Date.now() - start; }), ); } }
- Controller无需修改,模板直接使用
{{time}}即可:
@UseInterceptors(LoggingInterceptor) @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get('/') root(@Res() res: Response<any>) { return res.render( 'home', { layout: 'layout/layout_main', message: 'Hello world!' }, ); } }
内容的提问来源于stack exchange,提问作者Nastya Zolotareva
相关产品推荐
相关产品推荐

