angular服務端渲染
此項目未使用ng-zorro
基本準備
- 建立一個
angular
項目,ng new 項目名
- 運行
ng add @nguniversal/express-engine --clientProject 項目名
- 安裝一些服務端模塊
compression(頁面壓縮),http-proxy-middleware(http代理),multer(文件上傳)
服務端準備
平時開發過程當中,咱們仍然使用以前的模式,自備本地server
- 新建文件夾
server
,該server
主要用來存放服務端(相似之前的server.ts和fileUtils.ts)相關的文件
- 將server.ts文件移動到server文件夾中,修改
webpack.server.config.js
文件
entry: {
server: './server/server.ts' // 這裏改爲這樣
},
- 修改
server.ts
文件並增長fileUtils.ts
文件
import 'zone.js/dist/zone-node';
//新增
import { config } from './config/config.js'
import * as express from 'express';
//新增
import { createProxyMiddleware } from 'http-proxy-middleware'
import { join } from 'path';
import { APP_BASE_HREF } from '@angular/common';
//新增
import fileUtils from './fileUtils';
//新增
import * as multer from 'multer'
//新增
import * as compression from 'compression'
const app = express();
//新增
app.use(compression())
// 修改
const PORT = config.port;
// 修改
const DIST_FOLDER = join(process.cwd(), 'dist/dist');
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
const { AppServerModuleNgFactory, LAZY_MODULE_MAP, ngExpressEngine, provideModuleMap } = require('./dist/server/main');
app.engine('html', ngExpressEngine({
bootstrap: AppServerModuleNgFactory,
providers: [
provideModuleMap(LAZY_MODULE_MAP)
]
}));
app.set('view engine', 'html');
app.set('views', DIST_FOLDER);
//新增
var proxyConfig = createProxyMiddleware({
target: config.proxy.target,
changeOrigin: true,
pathRewrite: {
'^/api': ''
},
onProxyReq: function onProxyReq(proxyReq, req, res) {
}
});
//新增
app.use('/api', proxyConfig)
app.get('*.*', express.static(DIST_FOLDER, {
maxAge: '1y'
}));
let uploadSingle = multer({
dest: 'upload/'
});
//新增
app.post('/upload', uploadSingle.single('file'), function (req, res) {
// 這裏是文件上傳代碼 具體能夠查看詳細文件
});
app.get('*', (req, res) => {
res.render('index', { req });
});
app.listen(PORT, () => {
console.log(`Node Express server listening on http://localhost:${PORT}`);
});
-
server
中的config
也作了必定的修改,請仔細查看
服務端請求攔截
主要是針對須要渲染數據的頁面,須要服務器渲染的使用絕對路徑(這裏統一配置),詳細說明,官方已指出,地址
運行在服務端時,使用絕對URL發起請求,在瀏覽器中使用相對URL
- 新建
universal-interceptor.ts
文件
import { Injectable, Inject, Optional } from '@angular/core'
import { HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http'
import { Request } from 'express'
import { REQUEST } from '@nguniversal/express-engine/tokens'
import { config } from '../../server/config/config.js'
@Injectable()
export class UniversalInterceptor implements HttpInterceptor {
constructor(@Optional() @Inject(REQUEST) protected request: Request) { }
// 服務器渲染時 請求攔截,在這裏組裝爲絕對路徑
// 這裏也能夠對路徑作處理,而後api.service能夠不作處理
intercept(req: HttpRequest<any>, next: HttpHandler) {
let serverReq: HttpRequest<any> = req;
if (this.request) {
// 這裏的config和server的config是用同一個的,獲取接口的origin部分
let newUrl = config.proxy.target
if (!req.url.startsWith('/')) {
newUrl += '/'
}
newUrl += req.url;
serverReq = req.clone({ url: newUrl })
}
return next.handle(serverReq)
}
}
2. `app.server.module.ts`中引入`universal-interceptor`文件
瀏覽器端渲染數據
主要須要使用
TransferState
,
ServerTransferStateModule
,
BrowserTransferStateModule
- 在
app.module.ts
中引入BrowserTransferStateModule
- 在
app.server.module.ts
中引入ServerTransferStateModule
- 在須要服務端渲染的接口中作判斷
import { Injectable, PLATFORM_ID, Inject } from '@angular/core';
import { isPlatformBrowser, isPlatformServer } from '@angular/common'
import { HttpService } from './http.service'
@Injectable()
export class ApiService {
//prefix = '/api/v1'
prefix = '/api/mock/5e9575d592cee10f807aabe6/test'
prefix_render = '/mock/5e9575d592cee10f807aabe6/test'
constructor(private http: HttpService, @Inject(PLATFORM_ID) private platformId) { }
public getIndexData(params) {
// 在這裏判斷當前環境是不是瀏覽器端
let url = isPlatformBrowser(this.platformId) ? `${this.prefix}/test` : `${this.prefix_render}/test`
return this.http.get(url, params)
}
public upload(params) {
let url = '/upload'
return this.http.upload(url, params)
}
}
- 修改須要渲染的頁面
async getData() {
// 獲取存儲在TransferState中key爲INDEX_DATA的值
const kfcList: any[] = this.state.get(INDEX_DATA, null as any);
let res;
// 該判斷主要是爲了處理請求兩次的問題
if (!kfcList) {
res = await this.api.getIndexData({ page: this.page.pageNow, pageSize: this.page.pageSize })
this.list = res['list']
this.page.total = res['total']
// 獲取到值後將值存儲到state中
this.state.set(INDEX_DATA, res)
} else {
// 假如已經請求過了,則將值取出來
res = this.state.get(INDEX_DATA, { list: [], total: 1 })
this.list = res.list
}
// 判斷當前是不是在瀏覽器環境下,若是是的話計算頁碼
if (isPlatformBrowser(this.platformId)) {
this.calcPage(res.total) // 計算頁碼
}
}
// 這個請求主要是分頁請求,是瀏覽器請求,不適用上面那個方法,因此另外新定義了
async getDataByPage(pageNow) {
this.page.pageNow = pageNow
let res = await this.api.getIndexData({ page: pageNow, pageSize: this.page.pageSize })
this.list = res['list']
}