如何在启用SSR的Angular CLI应用中使用不支持Universal的模块
解决Angular Universal中引入不兼容模块的问题
嘿,我完全懂你这种崩溃的感觉——好不容易搭起SSR项目,引入个模块直接导致服务器静默挂掉,连报错都找不到,太闹心了!针对ngx-editor这类不支持Universal的模块,其实有几个办法可以尝试,不用急着换模块:
1. 只在浏览器环境加载模块(最常用)
核心思路是让服务器端完全跳过这个模块的加载,只在客户端初始化它。你可以这么做:
首先,不要直接在模块的
imports数组里添加NgxEditorModule,而是通过动态导入+平台判断来处理:import { NgModule, Inject, PLATFORM_ID } from '@angular/core'; import { isPlatformBrowser } from '@angular/common'; // 先声明模块变量,不直接导入 let NgxEditorModule: any; @NgModule({ imports: [ // 你的其他模块... // 服务器端时传入空数组,浏览器端才加载模块 isPlatformBrowser(PLATFORM_ID) ? NgxEditorModule : [] ] }) export class YourFeatureModule { constructor(@Inject(PLATFORM_ID) private platformId: Object) { // 只有在浏览器环境才动态导入模块 if (isPlatformBrowser(this.platformId)) { import('@kolkov/ngx-editor').then(module => { NgxEditorModule = module.NgxEditorModule; }); } } }在使用ngx-editor组件的地方,也要加一层平台判断,避免服务器端渲染该组件:
<ng-container *ngIf="isBrowser"> <ngx-editor [ngModel]="content"></ngx-editor> </ng-container>对应的组件里注入平台ID判断:
import { Component, Inject, PLATFORM_ID } from '@angular/core'; import { isPlatformBrowser } from '@angular/common'; @Component({...}) export class EditorComponent { isBrowser: boolean; constructor(@Inject(PLATFORM_ID) private platformId: Object) { this.isBrowser = isPlatformBrowser(this.platformId); } }
2. 模拟服务器端缺失的浏览器API
很多不支持SSR的模块崩溃,都是因为直接调用了window、document这类浏览器专属对象,服务器端没有这些全局变量。你可以在server.ts里模拟这些对象,让模块误以为自己在浏览器环境:
// 在server.ts的顶部添加这段代码 (global as any).window = { document: { createElement: () => ({ // 模拟ngx-editor需要的document方法,根据报错补充 setAttribute: () => {}, addEventListener: () => {} }), querySelector: () => null }, navigator: { userAgent: 'server-side-rendering' }, // 其他ngx-editor用到的window属性,根据调试报错补充 };
不过这个方法需要你调试模块的依赖,可能会需要补很多模拟对象,维护起来比较麻烦,适合简单场景。
3. 用路由守卫限制仅客户端访问
把用到ngx-editor的页面单独做成一个懒加载路由,并用守卫阻止服务器端导航到该页面:
先写一个浏览器专属守卫:
import { Injectable, Inject, PLATFORM_ID } from '@angular/core'; import { CanActivate, Router } from '@angular/router'; import { isPlatformBrowser } from '@angular/common'; @Injectable({ providedIn: 'root' }) export class BrowserOnlyGuard implements CanActivate { constructor( @Inject(PLATFORM_ID) private platformId: Object, private router: Router ) {} canActivate(): boolean { if (!isPlatformBrowser(this.platformId)) { // 服务器端访问时跳转到首页或其他兼容页面 this.router.navigate(['/']); return false; } return true; } }然后在路由配置里使用这个守卫:
const routes: Routes = [ // 其他路由... { path: 'edit-content', loadChildren: () => import('./editor/editor.module').then(m => m.EditorModule), canActivate: [BrowserOnlyGuard] } ];这样服务器端永远不会加载这个包含ngx-editor的模块,自然不会崩溃。
最后:如果以上方法都不顺畅
要是折腾下来发现维护成本太高(比如ngx-editor依赖的浏览器API太多,模拟不过来),那确实可以考虑换个支持SSR的编辑器模块,比如Quill的Angular封装版本,或者其他专门适配Universal的富文本编辑器组件。
内容的提问来源于stack exchange,提问作者Jay Bell
相关产品推荐
相关产品推荐

