Angular 16 SSR报错:无AppComponent2却提示需用new调用构造函数
Angular 15 迁移至 16 后 SSR 报错:
TypeError: Class constructor AppComponent2 cannot be invoked without 'new' 问题描述
我有一个使用独立组件(standalone components)的Angular应用,从版本15迁移到16后,运行SSR时出现以下错误:
TypeError: Class constructor AppComponent2 cannot be invoked without 'new'
但我的应用里只有AppComponent,根本不存在AppComponent2。相关代码如下:
server.ts(根目录)
import 'zone.js/node'; import 'reflect-metadata'; import {ngExpressEngine} from '@nguniversal/express-engine'; import * as express from 'express'; import {existsSync} from 'fs'; import {join} from 'path'; const domino = require('domino'); import {LRUCache} from 'lru-cache' import 'localstorage-polyfill' import {AppComponent} from "app/app.component"; // Set up LRU cache const ssrCache = new LRUCache({ max: 50, // Maximum number of items in the cache ttl: 1000 * 60 * 60 // Cache for 1 hour }); // The Express app is exported so that it can be used by serverless Functions. export function app(): express.Express { const server = express(); const distFolder = join(process.cwd(), 'dist/my-app/browser'); const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index'; const win = domino.createWindow(indexHtml); global['localStorage'] = localStorage; global['window'] = win; global['document'] = win.document; global['DOMTokenList'] = win.DOMTokenList; global['Node'] = win.Node; global['Text'] = win.Text; global['HTMLElement'] = win.HTMLElement; global['Event'] = win.Event; global['Event']['prototype'] = win.Event.prototype; global['KeyboardEvent'] = win.Event; global['MouseEvent'] = win.Event; const nonSSRRoutes = ['/app/*']; server.get(nonSSRRoutes, (req, res) => { console.log(`Not rendering SSR for ${req.originalUrl}`); res.sendFile(join(distFolder, 'index.html')); }); // Our Universal express-engine (found @ https://github.com/angular/universal/tree/main/modules/express-engine) server.engine('html', ngExpressEngine({ bootstrap: AppComponent, })); server.set('view engine', 'html'); server.set('views', distFolder); // Serve static files from /browser server.get('*.*', express.static(distFolder, { maxAge: '1y' })); // All regular routes use the Universal engine // @ts-ignore server.get('*', (req, res) => { const url = req.originalUrl; // Check if the request is for a cached route const cachedRoutes = ['/', '/page-not-found']; if (cachedRoutes.some(route => url === route || url.startsWith(route + '/?'))) { const cachedPage = ssrCache.get(url); if (cachedPage) { console.log('Serving from cache:', url); return res.send(cachedPage); } else { res.render(indexHtml, {req}, (err, html) => { if (err) { // @ts-ignore return req.next(err); } ssrCache.set(url, html); res.send(html); }); } } else { // Non-cached routes res.render(indexHtml, {req}); } }); return server; } function run(): void { const port = process.env['PORT'] || 4001; // Start up the Node server const server = app(); server.listen(port, () => { console.log(`Node Express server listening on http://localhost:${port}`); }); } // Webpack will replace 'require' with '__webpack_require__' // '__non_webpack_require__' is a proxy to Node 'require' // The below code is to ensure that the server is run only when not requiring the bundle. declare const __non_webpack_require__: NodeRequire; const mainModule = __non_webpack_require__.main; const moduleFilename = mainModule && mainModule.filename || ''; if (moduleFilename === __filename || moduleFilename.includes('iisnode')) { run(); } export * from './src/main.server';
main.ts
import { bootstrapApplication } from '@angular/platform-browser'; import {appConfig} from "app/app.config"; import {AppComponent} from "app/app.component"; bootstrapApplication(AppComponent, appConfig) .catch((err) => console.error(err));
src/main.server.ts(即你提供的“另一个server.ts”)
import { enableProdMode } from '@angular/core'; import { bootstrapApplication } from '@angular/platform-browser'; import { provideServerRendering } from '@angular/platform-server'; import { provideHttpClient } from '@angular/common/http'; import {AppComponent} from "app/app.component"; enableProdMode(); bootstrapApplication(AppComponent, { providers: [ provideHttpClient(), provideServerRendering() ] }).catch(err => console.error(err));
app.component.ts
@Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'], standalone: true, imports: [ RouterOutlet, CommonModule, ] }) export class AppComponent { title: string = 'my-app'; constructor( ) { } }
解决方法
1. 更新根目录server.ts的SSR引擎配置
Angular 16不再支持直接将组件传入ngExpressEngine的bootstrap参数,需要改为异步导入服务器端的bootstrap实例:
// 替换原来的ngExpressEngine配置 server.engine('html', ngExpressEngine({ bootstrap: () => import('./src/main.server').then(module => module.appServerBootstrap), }));
同时删除原来的import {AppComponent} from "app/app.component";,因为不再直接使用这个组件。
2. 修改src/main.server.ts导出bootstrap实例
将bootstrapApplication的返回值导出,供根目录server.ts调用:
import { enableProdMode } from '@angular/core'; import { bootstrapApplication } from '@angular/platform-browser'; import { provideServerRendering } from '@angular/platform-server'; import { provideHttpClient } from '@angular/common/http'; import { AppComponent } from "app/app.component"; enableProdMode(); // 导出bootstrap实例 export const appServerBootstrap = bootstrapApplication(AppComponent, { providers: [ provideHttpClient(), provideServerRendering() ] }).catch(err => console.error(err));
3. 清理并重新构建项目
# 删除旧的构建产物和依赖 rm -rf dist node_modules # 重新安装依赖 npm install # 构建SSR项目 npm run build:ssr # 启动SSR服务 npm run serve:ssr
错误原因
AppComponent2是Angular编译独立组件时生成的内部代理类,Angular 16的SSR引擎对独立组件的启动方式做了变更,旧的直接传入组件的方式无法正确实例化这个代理类,因此抛出错误。改用异步导入服务器端bootstrap实例的方式符合新版本的规范。
内容的提问来源于stack exchange,提问作者besttiktokporger
相关产品推荐
相关产品推荐

