1. 程式人生 > >Angular6 + Ng-Zorro專案開發總結(二)

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,就可以聯絡之前的讀取基地址相關聯了。