Angular6 + Ng-Zorro專案開發總結(二)
搭建通用類
由於之前用的是Abp框架,框架封裝的十分成熟,所以很多時候用起來都是知其然而不知其所以然。比如剛開始還沒有意識到每次請求都傳了token,因為在http方法的呼叫中,並沒有看到它新增header,知道後面看network才知道每次都傳了token,搜了全部的檔案,關於token的,只有封裝好的settoken方法和gettoken方法,到底是怎麼加入header的十分費解,後來,想了想估計是攔截器的作用,找了找abp原始碼,果然有httpInceptor.js,程式碼一讀,問題自然迎刃而解。
HttpInterceptor(HTTP攔截器)
關於這個東西的概念,官網是有十分清楚的解釋的,通俗的來說就是你在每次發起http請求時,最後的都要經過攔截器來加工一次,然後返回的http response也是要先經過攔截器處理加工。
自定義攔截器需要建立一個服務,服務中必須有的方法是intercept方法。
@Injectable()
export class InterceptorService implements HttpInterceptor {
constructor(
private message: NzMessageService,
private sessionService: SessionService) {
}
id: string = "";
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (this.id == "" && !req.url.includes('json')) {
this.id = this.message.loading('請稍後...', { nzDuration: 0 }).messageId;
}
let secureReq: HttpRequest<any> = req;
// const url = `${config.apiUrl}/api/`; //新增api統一字首
let modifiedHeaders = new HttpHeaders();
let token = document.cookie;
if (token) {
modifiedHeaders = req.headers.set('Authorization', `Bearer ${token}`);
}
secureReq = req.clone({
url: req.url,
headers: modifiedHeaders
});
const started = Date.now();
let ok: string;
if (req.url.includes('json')) {
return next.handle(secureReq);
}
else {
return next.handle(secureReq)
.pipe(
catchError((res: HttpResponse<any>) => {
console.log(res);
let msg = "";
switch (res.status) {
case 401:
msg = "身份驗證過期,請重新進入頁面";
break;
case 200:
msg = "身份驗證過期,請重新進入頁面";
break;
case 404:
msg = "找不到地址";
break;
case 403:
msg = "業務錯誤";
break;
case 500:
msg = "伺服器發生錯誤,請重試";
break;
}
this.showError(msg);
return Observable.create(res);
}),
finalize(() => {
const elapsed = Date.now() - started; //可計算出請求所消耗時間
const msg = `${req.method} "${req.urlWithParams}" ${ok} in ${elapsed} ms.`;
console.log(msg);
}),
mergeMap(
// Succeeds when there is a response; ignore other events
(event: any) => {
if (event.status == 200) {
this.id = "";
this.message.remove();
}
return Observable.create(observer => observer.next(event));
}),
);
}
showError(message: string) {
this.message.remove();
this.id = "";
this.message.error(message, { nzDuration: 2000 })
}
}
這是我在專案中實際用的interceptor,我做的比較多的修改是在請求進來的時候,載入了ng-zorro的message的loading訊息,這是個全域性的提示訊息,然後從cookie中讀取的之前存入的token,將其放入header中,執行next.handle()方法,執行http請求,通過catcherror來捕獲異常,根據異常的程式碼不同,來提示不同的錯誤。對了,這個地方用的pipe,catchError,finalize都是是rxjs6的新語法。
寫完自己的攔截器,接下來就需要去專案中入口的module.ts檔案中配置了,
providers: [
...
{ provide: HTTP_INTERCEPTORS, useClass: InterceptorService, multi: true },
],
配置完成後,重新生成就生效了。
Http服務
這個地方就是根據不同的專案有不同的實現方法了,我這個專案中,後臺用的技術是.Net Core,內部集成了swagger,這也為我提供了便利,我直接用NSwagStudio,來生成我的ts程式碼。貼個軟體的圖
左邊的位址列裡填寫swagger的json地址(一般來說只用修改前面的地址和埠就行了),填寫完了過後,create local copy,swagger的json檔案就生成出來了,然後右側的outputs中,可以選擇三種語言,這裡我只是需要Typescript,所以後面兩項我就沒有勾選了,然後再下面的TypeScript的tab欄裡面有一些配置,比如typescript的版本,angular和rxjs的版本,根據我的專案,我選擇了相對應的版本,然後generate output就可以了。
這個地方比較難以理解的就是api的基地址,在生成的程式碼中是以這種形式獲取的。
export const API_BASE_URL = new InjectionToken<string>('API_BASE_URL');
參考了一下之前用的abp框架,還是摸索出來了這個到底該怎麼用。
首先需要在根模組進行配置
providers: [
...
{ provide: API_BASE_URL, useFactory: getRemoteServiceBaseUrl },
],
這裡需要先從生成的檔案中import API_BASE_URL。
import { API_BASE_URL } from './common-service';
然後這個地方用到的useFactory,用的getRemoteServiceBaseUrl方法,是需要我們自己實現的,我這裡就在module檔案中實現了。
export function getRemoteServiceBaseUrl(): string {
return AppConsts.baseURL;
}
返回的是我之前在初始化的時候配置在我的一個常量檔案中的url。這樣操作完成後就大功告成了。
專案初始化
上面用的url,如果是寫死在ts檔案中是可以行得通的,但為了保證有些時候介面地址可能會變,如果是要改部署到環境上的專案,那就需要再生成,再部署一次才行,相當的麻煩,所以可以嘗試著把地址配在一個json檔案中,每次進來的時候讀取一次就行了,哪怕發生變化,也只需要在json檔案中做改動。
在assets資料夾下新建appconfig.json,檔案中寫好配置。
由於需要在專案啟動時讀取這個檔案,所以需要在初始化的時候進行讀取,這裡需要在根模組進行一些改動
{
provide: APP_INITIALIZER,
useFactory: appInitializerFactory,
deps: [Injector, PlatformLocation],
multi: true
},
這裡的APP_INITIALIZER和Injector是angular本身就自帶的,需要從angular/core中import。
import { APP_INITIALIZER, Injector } from '@angular/core';
實現的這個這個方法,appInitializerFactory則需要自己定義,同樣,我也在根模組中定義了。
export function appInitializerFactory(
injector: Injector,
platformLocation: PlatformLocation
) {
return () => {
return new Promise<boolean>((resolve, reject) => {
let httpclient: HttpClient = injector.get(HttpClient);
httpclient.get('assets/appconfig.json').toPromise().then((res: any) => {
AppConsts.baseURL = res.remoteServiceBaseUrl;
resolve(true);
},
(err) => {
reject(err);
})
});
}
}
這裡嘗試了很多種返回方法,最後還是隻有返回Promise才生效。賦值給AppConsts.baseURL,就可以聯絡之前的讀取基地址相關聯了。