转换为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
相关产品推荐
相关产品推荐

