我理解的 core 目錄

...

過了一遍 Angular 文檔 的小夥伴大體都會記得最佳實踐中提到過的有關CoreModule的一些解釋和說明,其實關於名字的命名不是強制性的,只要團隊中一致 pass,你把它命名爲XXXModule都無所謂。可是最主要的,仍是咱們須要理解「core」的做用以及在項目中發揮更好的做用和地位。html

我記錄下我項目中對「core」的一些拙略看法和搭配。前端

core目錄

縱觀整個Angular項目結構以及最佳實踐,咱們一般把項目按功能劃分文件夾,好比工具、共享、全局核心、頁面模塊組件、公用模塊組件等等,「core」在這裏至關於全局核心類型的範圍,那全局核心類型究竟是彙集了項目的哪些功能呢?個人理解是咱們能夠把 全局單例服務、只須要引入一次 的東西都歸併到這裏。typescript

  • 全局單例服務:這些服務在整個應用生命週期內只存在一個實例,也就是數據是全局互通的,而在 Angular 中實現單例服務就須要一箇中間提供商(module)來作中介,也就是所謂的「CoreModule」,而後在根模塊引用一次即可全局使用,這也是官方推薦的一種單例服務作法。然而在 Angular 6 + 版本後,官方爲 Injectable 裝飾器提供了 providedIn: 'root' 的選項,讓聲明的服務直接成爲單例服務,此後再不用經過「CoreModule」來提供服務,可是咱們的單例服務仍然能夠放在 core目錄 中,經過 路徑別名 配置來直接訪問服務,由於實際上,單例服務只會乖乖在 core目錄中 ,不會再有其餘東西來干擾。
  • 只需引入一次的?:什麼是項目中只須要引入一次的?舉個例子,全局錯誤處理、根路由數據預加載、http請求攔截器等。這些都是經過一次配置就能一直用到老的東西,並且不可能會有其餘兄弟來直接使用的東西,瓜熟蒂落就須要歸併到 core目錄 中,而且有的須要被「CoreModule」引用,有的須要被「AppModule」引用。

我列舉來幾個更加詳細的例子來講說這些類別:bootstrap

clipboard.png

應用初始數據加載

在開發單頁應用特別是管理系統的時候,可能項目的構成除了中心主系統還衍生了不少個子項目系統,這種狀況下登陸受權通常都是在主系統完成,而後先後端經過單點登陸確保子系統能使用。這時子系統通常都是一個新的項目,咱們都知道 Angular 提供了強大的路由功能,能夠經過路由守衛來預加載系統,然而咱們須要的受權信息是相對整個應用而不是某個路由而言的,那這個時候咱們就須要一個根級別的數據預加載功能來完成受權等功能。後端

Angular 仍是幫你開闢好了入口,這時咱們只須要一個APP_INITIALIZER就能夠完成預加載。前提是咱們定義好了預加載的數據操做邏輯,舉個例子:數組

/**
 * app 初始化前身份驗證操做
 */
@Injectable({
    providedIn: 'root'
})
export class AppInitAuthService {
    constructor(
        ...,
        private userInfoService: UserInfoService,
    ) { }

    /** 驗證當前token身份 */
    tokenAuth(): Promise<any> {
        return new Promise((resolve,reject) => {
            return this.userInfoService.getUserInfoServer().subscribe(res => {
                if(res.reasonCode == 'notLoggedIn'){
                    //未登陸 
                    //能夠進行取消受權處理
                    ...
                }else{
                    //獲取了受權數據,todo ...
                    resolve(true)
                }
            })
        })
     }
}

此處聲明瞭一個基本的用戶受權信息獲取服務,接下來咱們能夠直接經過APP_INITIALIZER來完成數據預加載功能,只須要在 CoreModule 中聲明剛纔提供的處理服務,Angular 會自動在根組件初始化前查詢並執行 APP_INITIALIZER 所注入的全部服務函數,因爲咱們提供的是一個 Promise 對象,因此 Angular 會等待執行結果:網絡

@NgModule({
    ...
    providers: [
        ...
        {
            provide: APP_INITIALIZER,
            multi: true,
            useFactory: (appInit: AppInitAuthService) => {
                return () => appInit.tokenAuth()
            },
            deps: [AppInitAuthService]
        }
    ]
})
export class CoreModule {
    constructor(
        ...
    ) {}
}

只要AppModule引用了CoreModule,項目會自動完成預受權處理功能,徹底無需其餘組件摻入。app


全局錯誤處理

有時候咱們須要全局錯誤處理機制。好比咱們編譯更新了項目版本,多個某個模塊功能,可是用戶這邊並無去實時刷新,當意外去到某個本來不存在的路由時 Angular 會捕獲到找不到模塊的錯誤,這是咱們就能夠提早在錯誤處理中去對用戶進行較友好的提示等等;又好比咱們會想要去接入前端監控平臺像 fundebug 等等,具體對實現方式也是同樣經過 Angular 提供的捕錯功能來實現。框架

一個最簡單的錯誤處理服務以下:ide

import { ErrorHandler } from '@angular/core'

export class HandleCommon extends ErrorHandler{
    constructor(){
        super()
    }

    handleError(error: Error){
        //注意調基類處理函數,否則會覆蓋默認行爲,好比控制檯不會看到報錯
        super.handleError(error)
        if (/Loading chunk [\d]+ failed/.test(error.message)) {
            //捕獲找不到模塊 (服務端目錄數據變更)
            ...
        } 
        //... 各類錯誤處理    
    }
}

而後咱們直接在AppModule中聲明一個 ErrorHandler 令牌對應的服務,就能夠實現全局錯誤監聽處理:

import { NgModule, ErrorHandler } from '@angular/core'
import { HandleCommon } from '../core'

@NgModule({
    ...
    providers: [
        ...
        {
            provide: ErrorHandler,
            useFactory: () => {
                return new HandleCommon()
            }
        }
    ],
    bootstrap: [
        AppComponent
    ]
})
export class AppModule { }

http請求攔截器

儘管 Angular 提供了十分漂亮的 HttpClient 給開發者舒服地進行網絡請求操做,可是有不少針對網絡請求的需求須要咱們本身去開發,像 http 超時攔截、token 攔截、錯誤處理攔截等等,這些也都屬於一次引用,全局使用的範疇。更漂亮的是 Angular 爲咱們提供了攔截器接口,咱們只管開發攔截器邏輯功能,調用及使用所有控制權都在框架內。因爲攔截器涉及比較多東西,這裏放一個最爲簡單的實現以下:

import {
    HttpInterceptor,
    HttpRequest,
    HttpHandler,
    HttpEvent
} from '@angular/common/http'
import { Injectable } from '@angular/core'
import { Observable } from 'rxjs'
import { tap } from 'rxjs/operators'

//攔截器 - 添加請求頭
@Injectable()
export class TokenInterceptor implements HttpInterceptor {

    constructor() { }

    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        //經過某些邏輯獲取 token
        let token = 'xxxx'
        if (token) {
            token = `Bearer ${token}`
            req = req.clone({
                setHeaders: {
                    Authorization: token
                }
            })
        }
        return next.handle(req)
    }
}

只須要在 CoreModule 中經過 HTTP_INTERCEPTORS 令牌來聲明咱們寫好的攔截器,框架會在正確的時機自動處理和調用攔截器邏輯:

@NgModule({
    providers: [
        ...,
        {
            provide: HTTP_INTERCEPTORS,
            useClass: TokenInterceptor,
            //必須設置,攔截器是個數組集合而不只僅只有一個
            multi: true
        }
    ]
})
export class CoreModule {
    constructor() {}
}

一些單例服務等等

應用中或多或少有一些須要在全局流通的數據,好比全局的用戶信息管理:

@Injectable({
    providedIn: 'root'
})
export class UserInfoService {
    //用戶數據 全局共享 數據流
    userInfo$: BehaviorSubject<UserInfo> = new BehaviorSubject<UserInfo>(null)

    constructor() { }

    /**
     * 獲取用戶數據
     */
    getUserInfoServer(): Observable<UserInfo> {
        ...
    }

    /**
     * 退出登陸
     */
    getUserLogoutServer(): Observable<boolean> {
        ...
    }
}

做爲頻繁被存取的介質,單例模式天然而然是它的特色,因此最好也一塊兒歸併到所謂的 core目錄 中。

因人而異

前面列舉了一些經常使用的類別來講明 core目錄 以及 「CoreModule」 存在的意義。除了一些須要「CoreModule」來做爲橋樑的例子,貌似 core目錄 並非必需要存放某些東西的,好比全局的單例對象就徹底能夠單獨使用其餘的文件夾來存放維護。是對的,沒有一個統一的標準來約束咱們究竟是要去如何組織代碼目錄結構,全部項目都是因人而異,本身以爲舒服的、可維護的才最重要。

本記錄只是爲了更加貼近官方最佳實踐而如此組織,純粹做爲一個記錄以及給你們的一個參考。

相關文章
相關標籤/搜索