我已成功使用@nguniversal/express-engine
运行有角度的通用应用程序。我无法工作的部分是在角度应用程序中注入了节点/表达式req
对象。遵循official documentation并不能解决我的问题。错误是
NullInjectorError:没有提供InjectionToken REQUEST!
在服务器上,我用以下摘录设置了快速引擎
app.engine('html', ngExpressEngine({
bootstrap: AppServerModuleNgFactory, // Give it a module to bootstrap,
providers: [ provideModuleMap(LAZY_MODULE_MAP) ]
}));
app.set('view engine', 'html');
app.set('views', join(DIST_FOLDER, 'browser'));
app.get('*.*', express.static(join(DIST_FOLDER, 'browser')));
并使用(1)呈现索引文件
app.get('*', (req: Request, res) => {
res.render(join(DIST_FOLDER, 'browser', 'index.html'), { req });
});
向应用程序组件中的注入看起来像
constructor (
@Inject(REQUEST) private request: Request
) {}
REQUEST
是从 @ nguniversal / express-engine / tokens 导入的。我还测试了渲染部分(1)中以下条目的添加,但这也不起作用。
providers: [ { provide: REQUEST, useValue: req } ]
还尝试注入@Optional()
装饰器,并使用PLATFORM_ID
来检查平台是否为server
,请求对象是否未被注入并null
。
您是否对如何成功使用angular本身访问req
对象有任何建议?
答案 0 :(得分:1)
您无法在浏览器中访问REQUEST
,它是从expressjs
传递到ng-universal
的服务器端对象。因此,在最坏的情况下,您的代码应该执行类似的操作
constructor(
@Inject(PLATFORM_ID) private platformId,
@Optional() @Inject(REQUEST) private request
) {
doSomethingWithRequestIfServer();
}
someOtherMethod() {
doSomethingWithRequestIfServer();
}
doSomethingWithRequestIfServer() {
if (isPlatformServer(this.platformId)) {
// should see this in stdout of node process, or wherever node logs
console.log('rendering server side for request:', req);
/* use req */
} else {
// browser console should print null
console.log('working browser side, request should be null', req);
}
}
然后,在浏览器中时,您的应用程序将不会查看“请求”,而在服务器端呈现时,它将显示。即就像您的服务器端渲染将一次运行您的角度应用程序 ,因为渲染结果将HTML发送到浏览器。然后浏览器将再次引导Angular应用程序,并从服务器渲染完成的位置提取。
在最佳情况下,为避免if
和平台检查,我建议对应用程序进行布局,以便仅在服务器端提供服务器端代码,而在浏览器中提供浏览器端代码。这肯定需要更多的代码,但是从维护的角度来看将更加直接。
(在下面键入快速的猴子)
公共接口声明
// common.ts
export abstract class MyServiceBase {
abstract doSomething(): void;
}
// if implementations of service will be provided only in Angular implementation, InjectionToken can be used.
export const MY_SERVICE = new InjectionToken<MyServiceBase>('MY_SERVICE');
// otherwise, if server side implementation will be injected by from node process, then should be string only. (For alternative illustrated below in the end).
// export const MY_SERVICE = 'MY_SERVICE';
应该在服务器和浏览器上呈现的通用应用程序组件。
// app.component.ts
@Component({
// skipped
})
export class AppComponent {
constructor(@Inject(MY_SERVICE) myService: MyServiceBase) {
myService.doSomething();
}
}
适用于服务器端和浏览器端的通用应用程序代码。
// app.module.ts
@NgModule({
declarations: [AppComponent],
bootstrap: [AppComponent]
})
export class AppModule {}
// app-browser.service.ts
@Injectable()
export class MyServiceForBrowser extends MyServiceBase {
constructor() {
console.log('MyService browser implementation');
}
doSomthing(): void {
// do something meaningful in browser
}
}
引导此操作,而不是默认的AppModule
来分隔实施设置。
// app-browser.module.ts
@NgModule({
imports: [AppModule],
providers: [{provide: MY_SERVICE, useClass: MyServiceForBrowser}],
bootstrap: [AppComponent]
})
export class AppBrowserModule {
}
角度应用程序的服务器端 如果您可以立即在Angular应用程序中实现服务器端功能,那么。
// app-server.service.ts
@Injectable()
export class MyServiceForServer extends MyServiceBase {
constructor(@Inject(REQUEST) private request) {
console.log('MyService server implementation');
}
doSomthing(): void {
// do something meaningful on server
console.log('request is', this.request);
}
}
要从ngExpressEngine
引导的服务器端模块。
// app-server.module.ts
@NgModule({
imports: [AppModule],
providers: [{provide: MY_SERVICE, useClass: MyServiceForServer}],
bootstrap: [AppComponent]
})
export class AppServerModule {}
服务器的备用服务器端
或者,甚至可以从服务器代码中提供MyServiceForServer
。在这种情况下,无需在角度实现中提供实现:
// app-server.module.ts
@NgModule({
imports: [AppModule],
bootstrap: [AppComponent]
})
export class AppServerModule {}
相反,将其编写为普通的服务器端代码:
export class MyServiceForServer extends MyService {
constructor(private request) {
console.log('MyService server implementation');
}
doSomthing(): void {
// do something meaningful on server
console.log('request is', this.request);
}
}
并注入为external
值:
app.get('*', (req: Request, res) => {
// construct the server side service instance
const myService = new MyServiceForServer(req);
// render server side with service instance provided
res.render(join(DIST_FOLDER, 'browser', 'index.html'), {
providers: [{provide: MY_SERVICE, useValue: myService}],
req
});
});
在这种情况下,请确保您的MY_SERVICE
令牌是纯字符串,而不是InjectionToken
实例。
答案 1 :(得分:0)
在package.json中设置版本:
"@types/express": "^4.17.4",
"@types/node": "^12.11.1",