This article has not been translated, hope that your can PR to translated it. Help us!
服务端渲染(SSR)
本文描述的是 NG-ALAIN 如何支持服务端渲染(SSR)。
NG-ALAIN 不推荐在中后台使用服务端渲染(SSR),这是因为中后台本身对于SSR所带来的好处远大于开发带来的麻烦,但不管怎么样在许多人的要求下,从
9.5
版本开始,已经对所有@delon/*
类库支持服务端渲染。
开始之前
在开始之前请先阅读以下文章,它们能够更加快速让你了解 Angular 服务端渲染是如何工作的:
教程
添加 @nguniversal/express-engine
在一个完整的 NG-ALAIN 项目下,执行以下命令:
ng add @nguniversal/express-engine
最后运行:
npm run dev:ssr
此时会以 SSR 的形式运行 NG-ALAIN。
但如果采用默认 LocalStorageStore
来存储 Token 的情况下,会提示找不到 localStorage
的错误,这是因为服务端并没有这些,它们可能还包含 window
、document
、sessionStorage
等。
因此,要想在中后台很好的支持 SSR,需要分析所依赖的第三方类库是否支持 SSR,如果没有必须手动处理在服务端下不渲染这些组件。
丢失Token
服务端是无状态的,因此判断请求是否有效授权,目前通用的做法是将 Token 存储在 Cookie 下,在服务端接收请求时再根据 Cookies 来获取 Token 信息。
虽然 NG-ALAIN 提供 CookieStorageStore
但它并不支持 SSR,因为它所依赖的 js-cookie 第三类库并不支持,因此需要手动构建针对 SSR 的 Token 持久化存储。
推荐使用 @ngx-utils/cookies 来处理 Cookies,它同时支持客户端与服务端。
注意: 受限于 #20 的原因,由于一直未处理,有人专门解决了这个问题并发布一个新类库 ngx-utils-cookies-port,暂时只能使用它来代替
@ngx-utils/cookies
,用法一模一样只是模块名换一下,在修复之后再换回来。
要创建一个符合 @delon/auth
接口持久化存储类,只需要继承 IStore
即可,例如:
import { Injectable } from '@angular/core';
import { IStore, ITokenModel } from '@delon/auth';
import { CookiesService } from 'ngx-utils-cookies-port';
@Injectable()
export class AuthStorageStore implements IStore {
constructor(private cookies: CookiesService) {}
get(key: string): ITokenModel {
return JSON.parse(this.cookies.get(key) || '{}') || {};
}
set(key: string, value: ITokenModel | null): boolean {
this.cookies.put(key, JSON.stringify(value));
return true;
}
remove(key: string) {
this.cookies.remove(key);
}
}
最后,在 global-config.module.ts
内重新注册它:
const alainProvides = [
{ provide: ALAIN_CONFIG, useValue: alainConfig },
+ { provide: DA_STORE_TOKEN, useClass: AuthStorageStore },
];
注意:这里依然需要注册新增的模块,方法请参考 @ngx-utils/cookies 说明。
@ngx-utils/cookies
内部会根据 REQUEST
来获取当前的请求头信息,因此,我们还需要修改 server.ts
:
// All regular routes use the Universal engine
server.get('*', (req, res) => {
res.render(indexHtml, {
req,
+ res,
providers: [
{ provide: APP_BASE_HREF, useValue: req.baseUrl },
+ { provide: 'REQUEST', useValue: req },
+ { provide: 'RESPONSE', useValue: res },
],
});
});