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

转换为Angular Universal(SSR)后支付结果页面崩溃求助

Angular Universal(SSR)支付结果页崩溃问题解决

问题描述

用户完成第三方支付后跳转回带查询参数的支付结果确认页,非SSR环境运行正常,但切换到Angular Universal(SSR)后页面崩溃,报错提示getBoundingClientRect is not a function,但业务代码中并未直接调用该方法。

相关组件代码

@Component({
  selector: 'app-payment-result',
  templateUrl: './payment-result.component.html',
  styleUrls: ['./payment-result.component.css']
})
export class PaymentResultComponent implements OnInit {
  isBusy: boolean;
  data: PaymentResult;
  success: boolean;
  isPaymentInformationBusy = true;
  paymentInformation: PaymentInformationResult[];

  constructor(
    private route: ActivatedRoute,
    private router: Router,
    private requestService: RequestService,
    private modalService: ModalService
  ) {
    this.route.queryParams.subscribe(params => {
      console.log(params)
      if (params) {
        this.getData(params?.merchantOrderId);
        this.success = params?.resultCode === '00';
      } else {
        this.router.navigate(['/']);
      }
    });
  }

  ngOnInit(): void { }

  getData(transactionNo: string): void {
    this.isBusy = true;
    this.requestService.get<PurchaseTransactionResult>(
      'purchase-transaction',
      obj => {
        this.data = obj;
        this.getPaymentInformation(obj?.PaymentMethodInformation?.ID);
      },
      failed => {
        console.log(failed);
      },
      error => {
        console.log(error);
      },
      () => {
        this.isBusy = false;
      },
      {TransactionNo: decodeURIComponent(transactionNo)}
    );
  }

  getPaymentInformation(id: string): void {
    this.isPaymentInformationBusy = true;
    this.requestService.get<PaymentInformationResult[]>(
      `purchase-transaction/payment-method-information/${id}`,
      obj => {
        this.paymentInformation = obj;
        ScriptLoader.loadScript('/assets/js/payment.js', 'payment');
      },
      failed => {
        console.log(failed);
      },
      error => {
        console.log(failed);
      },
      () => {
        this.isPaymentInformationBusy = false;
      });
  }

  getArray(count: number): any[] {
    return new Array(count);
  }
}

崩溃报错信息

/Users/marko/appname/web/dist/web/server/main.js:111913
              throw error;
              ^

TypeError: (element || document.body).getBoundingClientRect is not a function
    at reflow (/Users/marko/appname/web/dist/web/server/main.js:218104:37)
    at ngbRunTransition.animation (/Users/marko/appname/web/dist/web/server/main.js:226946:11)
    at ngbRunTransition (/Users/marko/appname/web/dist/web/server/main.js:218181:17)
    at Object.next (/Users/marko/appname/web/dist/web/server/main.js:226944:7)
    at ConsumerObserver.__webpack_modules__.3317.ConsumerObserver.next (/Users/marko/appname/web/dist/web/server/main.js:89909:33)
    at SafeSubscriber.__webpack_modules__.3317.Subscriber._next (/Users/marko/appname/web/dist/web/server/main.js:89876:26)
    at SafeSubscriber.__webpack_modules__.3317.Subscriber.next (/Users/marko/appname/web/dist/web/server/main.js:89847:18)
    at /Users/marko/appname/web/dist/web/server/main.js:95558:32
    at OperatorSubscriber._this._next (/Users/marko/appname/web/dist/web/server/main.js:92037:21)
    at OperatorSubscriber.__webpack_modules__.3317.Subscriber.next (/Users/marko/appname/web/dist/web/server/main.js:89847:18)

A server error has occurred.
node exited with 1 code.

解决方法

1. 定位问题根源

报错栈显示问题来自ngbRunTransition,也就是ng-bootstrap的过渡动画逻辑。SSR运行在Node.js环境,没有浏览器的DOM API,getBoundingClientRect是浏览器专属方法,服务端执行动画相关代码时就会触发这个错误。

2. 具体修复步骤

(1)仅在浏览器环境执行依赖DOM的代码

使用Angular提供的isPlatformBrowser工具类,确保脚本加载、动画触发等操作只在浏览器端执行:

  • 先导入相关工具类:
    import { isPlatformBrowser, PLATFORM_ID } from '@angular/common';
    import { Inject } from '@angular/core';
    
  • 在组件构造函数中注入PLATFORM_ID:
    constructor(
      private route: ActivatedRoute,
      private router: Router,
      private requestService: RequestService,
      private modalService: ModalService,
      @Inject(PLATFORM_ID) private platformId: Object
    ) {}
    
  • 修改getPaymentInformation中的脚本加载逻辑:
    obj => {
      this.paymentInformation = obj;
      // 仅在浏览器环境加载脚本
      if (isPlatformBrowser(this.platformId)) {
        ScriptLoader.loadScript('/assets/js/payment.js', 'payment');
      }
    }
    

(2)禁用服务端的ng-bootstrap动画

在服务端模块app.server.module.ts中配置ng-bootstrap,直接禁用所有过渡动画:

import { NgModule } from '@angular/core';
import { ServerModule } from '@angular/platform-server';
import { AppModule } from './app.module';
import { AppComponent } from './app.component';
import { NgbModule, NgbConfig } from '@ng-bootstrap/ng-bootstrap';

@NgModule({
  imports: [
    AppModule,
    ServerModule,
    NgbModule
  ],
  providers: [
    {
      provide: NgbConfig,
      useValue: { animation: false }
    }
  ],
  bootstrap: [AppComponent]
})
export class AppServerModule {}

(3)检查模板中的DOM依赖逻辑

如果组件模板中使用了ng-bootstrap的模态框、弹窗等带动画的组件,要确保它们不会在服务端渲染时自动触发,比如用*ngIf结合isPlatformBrowser控制渲染:

<ng-template #modalContent>...</ng-template>
<button *ngIf="isBrowser" (click)="openModal(modalContent)">打开弹窗</button>

组件中添加isBrowser属性:

isBrowser: boolean;

constructor(@Inject(PLATFORM_ID) private platformId: Object) {
  this.isBrowser = isPlatformBrowser(this.platformId);
}

3. 额外排查

检查payment.js脚本内部是否有直接操作DOM或调用浏览器API的代码,如果有,同样要确保这些代码只在浏览器环境下执行。

内容的提问来源于stack exchange,提问作者marko

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 10:25:21