10個(gè)你不知道的Angular實(shí)用特性

1.應(yīng)用初始化器 APP_INITIALIZER

有時(shí)您希望在應(yīng)用程序啟動(dòng)后立即運(yùn)行某些代碼,例如加載一些設(shè)置以確定應(yīng)用程序的各個(gè)部分的顯示方式。假設(shè)這個(gè)設(shè)置是異步加載的,這可能會(huì)有問(wèn)題,因?yàn)樵诋惒秸?qǐng)求還沒(méi)有響應(yīng)回來(lái)的時(shí)候,應(yīng)用程序繼續(xù)引導(dǎo)過(guò)程。

Angular為我們提供了一個(gè)簡(jiǎn)單的解決方案。我們可以訪問(wèn)一個(gè)APP_INITIALIZER令牌,我們可以用它來(lái)添加作為應(yīng)用初始化過(guò)程的一部分調(diào)用的函數(shù)。這些函數(shù)可以返回一個(gè)promise,因此我們也可以將它們用于異步事件。

export function onInit(settingsService: SettingsService) {
    return () => settingsService.getSettings();
}

@NgModule({
  providers: [
    SettingsService,
    { provide: APP_INITIALIZER, useFactory: onInit, deps: [SettingsService], multi: true }
  ]
})
export class AppModule { }

2.手勢(shì)識(shí)別

Angular預(yù)先構(gòu)建了所有瀏覽器事件監(jiān)聽(tīng)器,主要針對(duì)PC用戶和傳統(tǒng)的鼠標(biāo)和鍵盤(pán)事件,以及一些基本的觸摸識(shí)別事件。然而,有許多觸摸手勢(shì)可以用于更好的用戶體驗(yàn),例如平移,縮放,滑動(dòng)和旋轉(zhuǎn)等等。Angular沒(méi)有標(biāo)配這些事件監(jiān)聽(tīng)器,但是它為HammerJS提供了很好的支持,HammerJS是一個(gè)專(zhuān)為處理此類(lèi)事件而設(shè)計(jì)的庫(kù)。

<div (tap)="onTap($event)"
     (press)="onPress($event)"
     (pressup)="onPressUp($event)"
     (pinch)="onPinch($event)"
     (pinchstart)="onPinchStart($event)"
     (pinchmove)="onPinchMove($event)"
     (pinchend)="onPinchEnd($event)"
     (pinchcancel)="onPinchCancel($event)"
     (pinchin)="onPinchIn($event)"
     (pinchout)="onPinchOut($event)"
     (swipe)="onSwipe($event)"
     (swipeleft)="onSwipeLeft($event)"
     (swiperight)="onSwipeRight($event)"
     (swipeup)="onSwipeUp($event)"
     (swipedown)="onSwipeDown($event)"
     (rotate)="onRotate($event)"
     (rotatestart)="onRotateStart($event)"
     (rotatemove)="onRotateMove($event)"
     (rotateend)="onRotateEnd($event)"
     (rotatecancel)="onRotateCancel($event)">
</div>

3.模板類(lèi)型檢查(以及如何繞開(kāi))

在TypeScript中,我們可能沒(méi)有足夠的類(lèi)型定義,例如,我們可能正在使用沒(méi)有提供它們的庫(kù),并且我們沒(méi)有可用的時(shí)間或資源來(lái)創(chuàng)建它們。TypeScript允許我們?cè)谶@種情況下使用一種特殊類(lèi)型,any它只是告訴編譯器“保持安靜,我知道我在做什么”......

作為AOT編譯過(guò)程的一部分,不僅會(huì)對(duì)TypeScript代碼執(zhí)行類(lèi)型檢查,還會(huì)鍵入檢查模板。這對(duì)于檢測(cè)錯(cuò)誤很有用,例如拼寫(xiě)錯(cuò)誤或訪問(wèn)不存在的屬性。然而,與TypeScript一樣,有時(shí)您可以“更好地了解”,并且正在訪問(wèn)某些確實(shí)存在的屬性,即使編譯器不知道它也是如此。幸運(yùn)的是,通過(guò)使用$any類(lèi)型轉(zhuǎn)換功能,我們有一個(gè)簡(jiǎn)單的解決方案,例如:

<p>{{ $any(user).name }}</p>

4.Provider 的作用域

一般在創(chuàng)建服務(wù)Service時(shí),要么將它添加 到NgModule中的providers中:

@NgModule({
    providers: [ApiService]
})

要么在Angular6+中:

@Injectable({
    providedIn: 'root'
})
export class ApiService {}

由于這種方式是單例模式,所有使用它的地方都會(huì)創(chuàng)建一個(gè)對(duì)象,但是都指向同一個(gè)引用(在 lazy loaded模塊中使用的services 是個(gè)例外)。有時(shí)它可以在父子組件間方便地通信,這個(gè)時(shí)候services 中的數(shù)據(jù)改變不會(huì)丟失。但是,有時(shí)候如果有不同的組件,需要不同的數(shù)據(jù) 需要重新初始化service中的共享區(qū)時(shí),就有問(wèn)題了:services 無(wú)法區(qū)分不同的組件。

5.裝飾器: Host, Self, SkipSelf & Optional

Angular 提供了大量的裝飾器,我們天天在使用的就有:@NgModule, @Component, @Directive and @Injectable 。Angular有一些額外的裝飾器,允許我們?yōu)橐蕾囎⑷胩砑宇~外的約束以及解決依賴。

首先,知道Injector的原理很重要。一個(gè)組件Component要查找一個(gè)service來(lái)依賴注入,會(huì)先檢查自己是不是已經(jīng)注冊(cè)了Injectables 。再查父組件是否注冊(cè)了。直到找到了,否則會(huì)報(bào)錯(cuò):there was no provider found。

@Self

用來(lái)限制尋找Injectable對(duì)象時(shí),向上尋找的Injector 樹(shù)的層級(jí)。它明確了查當(dāng)前說(shuō)好的Component,再多一點(diǎn)都不查~

@Host

@Self很像,也是限制向上查找的層級(jí)。它通過(guò)一些特殊情況來(lái)確認(rèn)這個(gè)Component本身的作用域:
特殊情況一:如果directive正在請(qǐng)求一個(gè)injectable時(shí),就要看使用這個(gè)directiveComponent了;

特殊情況二:如果使用的是 <ng-content> 來(lái)插入的這個(gè)Component, 那么這個(gè)<ng-content> 元素也會(huì)被檢查.

@SkipSelf

正好和@Self相反 , 不檢查自己,而檢查父節(jié)點(diǎn)的injectable對(duì)象.

@Optional

在我們找不到Injectable對(duì)象時(shí), @Optional很有用. 如果找injectable對(duì)象沒(méi)找到時(shí), 保護(hù)不拋異常.你只需要保證沒(méi)有injectable對(duì)象時(shí),你的代碼依然能正常運(yùn)行.

6.Http Interceptors

如果有鑒權(quán)的時(shí)候,使用它會(huì)非常有用.比如要給每個(gè)請(qǐng)求設(shè)置一個(gè)header時(shí).

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const token = localStorage.get('token');
      
    request = request.clone({
      setHeaders: {
        Authorization: `Bearer ${token}`
      }
    });
    return next.handle(request);
  }
}

然后在我們的NgModule:

@NgModule({
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: TokenInterceptor,
      multi: true
    }
  ]
})
export class AppModule {}

7.Route Guards

當(dāng)需要用戶權(quán)限和安全性時(shí),路由器附帶了一個(gè)名為Guards的強(qiáng)大功能,它允許我們檢查用戶是否被允許訪問(wèn)應(yīng)用程序中的特定頁(yè)面并阻止他們導(dǎo)航到它必要。
但是,不要想著在前端做安全,因?yàn)樗梢院茌p松地就被偽造,最好還是要做服務(wù)端的鑒權(quán) 。

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(private authService: AuthService) {}

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
    return this.authService.isLoggedIn();
  }
}
// 這樣去使用
const routes: Routes = [
  {
    path: 'profile',
    component: ProfileComponent,
    canActivate: [AuthGuard]
  }
];

8.RxJS訂閱(Subscriptions)

當(dāng)我們有非常多的可觀察對(duì)象(observables)時(shí),我們經(jīng)常這樣寫(xiě):

export class AppComponent implements OnDestroy {
    
    private _mouseDownSubscription: Subscription;
    private _mouseUpSubscription: Subscription;
    private _mouseMoveSubscription: Subscription;
    
    constructor(@Inject(DOCUMENT) document: any) {
        this._mouseDownSubscription = fromEvent(document, 'mousedown')
            .subscribe(() => { /*...*/ });
        
        this._mouseUpSubscription = fromEvent(document, 'mouseup')
            .subscribe(() => { /*...*/ });
        
        this._mouseMoveSubscription = fromEvent(document, 'mousemove')
            .subscribe(() => { /*...*/ });
    }
    
    ngOnDestroy(): void {
        this._mouseDownSubscription.unsubscribe();
        this._mouseUpSubscription.unsubscribe();
        this._mouseMoveSubscription.unsubscribe();
    }
}

這樣寫(xiě),就開(kāi)始變得混亂和不可維護(hù),建議用這些更好的方法:

(1)使用Subscriptionadd方法

export class AppComponent implements OnDestroy {
    
    private _subscription = new Subscription();
    
    constructor(@Inject(DOCUMENT) document: any) {
        this._subscription.add(fromEvent(document, 'mousedown')
            .subscribe(() => { /*...*/ }));
        
        this._subscription.add(fromEvent(document, 'mouseup')
            .subscribe(() => { /*...*/ }));
        
        this._subscription.add(fromEvent(document, 'mousemove')
            .subscribe(() => { /*...*/ }));
    }
    
    ngOnDestroy(): void {
        this._subscription.unsubscribe();
    }
}

(2)使用

個(gè)人非常推薦這種方式,效果也很好。在RxJS中,我們有許多操作可以自動(dòng)取消訂閱。例如,在某些情況下,我們實(shí)際上只關(guān)心第一個(gè)值,或者直到滿足某個(gè)條件,在這些情況下?lián)碛袑⒆詣?dòng)取消訂閱的first和takeWhile運(yùn)算符。另一個(gè)有用的是takeUntil,當(dāng)另一個(gè)emits時(shí) 我們可以使用它來(lái)取消訂閱所有的observable,可以像這樣使用:

export class AppComponent implements OnDestroy {
    
    private _onDestroy = new Subject<void>();
    
    constructor(@Inject(DOCUMENT) document: any) {
        fromEvent(document, 'mousedown').pipe(takeUntil(this._onDestroy))
            .subscribe(() => { /*...*/ }));
        
        fromEvent(document, 'mouseup').pipe(takeUntil(this._onDestroy))
            .subscribe(() => { /*...*/ }));
        
        fromEvent(document, 'mousemove').pipe(takeUntil(this._onDestroy))
            .subscribe(() => { /*...*/ }));
    }
    
    ngOnDestroy(): void {
        this._onDestroy.next();
        this._onDestroy.complete();
    }
}

9.預(yù)加載 懶模塊

我們可以在路由中方便地設(shè)置懶模塊,從而減少總的打包JS大小,進(jìn)而提升初次打開(kāi)頁(yè)面的速度。懶加載的模塊是在訪問(wèn)某路由時(shí),才加載相應(yīng)的JS。而這里要說(shuō)的是提前加載它們:

imports: [
  RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules })
],

另外,這個(gè)加載策略也可以很方便地自定義,比如只預(yù)加載常用的模塊,而不預(yù)加載所有的。
https://angular.io/api/router/PreloadAllModules

10.不可變的只讀類(lèi)型

雖然這不是直接Angular特定的,但在使用Angular功能(如OnPush更改檢測(cè)和NGRX等不可變狀態(tài)管理)時(shí)可以提供很大幫助。

讓我們從檢查OnPush變化檢測(cè)開(kāi)始。這是Angular通過(guò)完全避免組件來(lái)優(yōu)化變更檢測(cè)的一種方法,除非它知道@Input已經(jīng)改變了。

這是一個(gè)很好的優(yōu)化,可以顯著提高應(yīng)用程序的性能,但它確實(shí)存在一些限制,最明顯的是,如何檢查輸入的變化。

舉個(gè)例子,Angular使用引用相等性檢查來(lái)確定輸入是否已更改,如下所示:

if (oldValue !== newValue) {
    // value has changes
}

當(dāng)輸入值是基本類(lèi)型(如數(shù)字,布爾值或字符串)時(shí),沒(méi)問(wèn)題。但是在使用數(shù)組或?qū)ο髸r(shí)我們可能會(huì)遇到一些麻煩。JavaScript不執(zhí)行任何類(lèi)型的深度檢查,作為等式檢查的一部分,它只是檢查引用,例子:

let obj1 = { name: 'Michael Scott' };
let obj2 = obj1;

// Change the name property in obj2
obj2.name = 'Dwight Schrute';

// Perform an equality check
if (obj1 === obj2) {
    // this will be true!!
}

所以即使我們更改了obj2引用上的屬性仍然是相同的,所以就JavaScript而言,這兩個(gè)對(duì)象仍然是相等的!

對(duì)于數(shù)組也是如此,例如,將新值推送到數(shù)組也將在相等測(cè)試中返回true,即使數(shù)組中有新項(xiàng)也是如此!

在這種情況下,更新對(duì)象屬性或?qū)㈨?xiàng)添加到數(shù)組不會(huì)在啟用了OnPush更改檢測(cè)的組件中觸發(fā)更改檢測(cè)。

這就是不可變對(duì)象和數(shù)組的概念所在。不是更改對(duì)象中的屬性或?qū)㈨?xiàng)目推送到數(shù)組,而是根據(jù)原始對(duì)象創(chuàng)建新對(duì)象或數(shù)組,但需要進(jìn)行更改。通過(guò)進(jìn)行此更改,我們更改了對(duì)象引用,允許我們的相等性檢查以了解發(fā)生了更改。

使用新的JavaScript Spread運(yùn)算符,我們可以使這很簡(jiǎn)單,例如:

let obj1 = { name: 'Michael Scott', company: 'Dunder Mifflin' };
let obj2 = { ...obj1, name: 'Dwight Schrute' };

if (obj1 === obj2) {
   // Objects are now not equal
}

我們還可以使用spread運(yùn)算符將項(xiàng)添加到數(shù)組并創(chuàng)建新引用:

let employees = ['Pam Beasley'];
employees = [...employees, 'Jim Halpert'];

有時(shí)候很難確保我們總是創(chuàng)建一個(gè)新的對(duì)象或數(shù)組,特別是在大量的數(shù)組函數(shù)可用的情況下,其中一些可以執(zhí)行,有些則不可用??纯催@里瘋狂:https//doesitmutate.xyz/

這就是TypeScript真正有用的地方。我們可以告訴TypeScript在對(duì)象或數(shù)組上強(qiáng)制實(shí)現(xiàn)不變性,這意味著每當(dāng)我們對(duì)其進(jìn)行更改而不更改其引用時(shí),我們都會(huì)遇到構(gòu)建錯(cuò)誤。這樣做非常簡(jiǎn)單!只需開(kāi)始使用ReadonlyReadonlyArray類(lèi)型!

// Before:
const employee: Employee = { name: 'Michael Scott' };

// This will compile fine
employee.name = 'Oscar Martinez';

// After:
const employee: Readonly<Employee> = { name: 'Michael Scott' };

// This will produce an error
employee.name = 'Kevin Malone';

我們還可以為數(shù)組,映射和集使用只讀類(lèi)型:

const employees: ReadonlyArray<Employee>;
const employees: ReadonlySet<Employee>;
const employees: ReadonlyMap<string, Employee>;

您不需要包含像Immutable.js這樣的庫(kù)來(lái)使您的代碼不可變。讓TypeScript在構(gòu)建時(shí)為您完成!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 使用Angular(Angular 2 及以上版本)開(kāi)發(fā)程序時(shí),裝飾是一個(gè)核心概念。還有一個(gè)正式的TC39 提案,...
    OSC開(kāi)源社區(qū)閱讀 1,179評(píng)論 0 11
  • 前兩天,Jigsaw七巧板上來(lái)了個(gè)issue https://github.com/rdkmaster/jigsa...
    阿踏閱讀 3,446評(píng)論 0 5
  • Foreword: 首先那要說(shuō)明下,以下是我看到的一篇文章,但是原文是英文的,我只是做一個(gè)搬運(yùn)工把他搬過(guò)來(lái)~主要也...
    Howie126313閱讀 11,081評(píng)論 4 40
  • 數(shù)一數(shù)日子,這個(gè)假期真的沒(méi)剩多少了,雖然本來(lái)就不多。我到底都做了些什么,房子沒(méi)收拾,作業(yè)也沒(méi)做,每天肆意揮灑時(shí)間,...
    云去兮無(wú)歸閱讀 178評(píng)論 0 0
  • 那一天, 你突然問(wèn)起, 我在哪里? 你說(shuō), 你好想找我去玩, 而我, 明知道不可能, 卻還是一臉堅(jiān)定的說(shuō), 只要你...
    紅塵若夢(mèng)_閱讀 165評(píng)論 3 2

友情鏈接更多精彩內(nèi)容