You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从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中传入模板:

  1. 修改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;
        }),
      );
  }
}
  1. 修改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传递变量,所有模板可直接访问,无需手动传入:

  1. 修改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;
        }),
      );
  }
}
  1. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 19:43:11