10個你不知道的Angular實用特性

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

有時您希望在應(yīng)用程序啟動后立即運行某些代碼锈麸,例如加載一些設(shè)置以確定應(yīng)用程序的各個部分的顯示方式趾徽。假設(shè)這個設(shè)置是異步加載的侧巨,這可能會有問題,因為在異步請求還沒有響應(yīng)回來的時候躯枢,應(yīng)用程序繼續(xù)引導(dǎo)過程则吟。

Angular為我們提供了一個簡單的解決方案。我們可以訪問一個APP_INITIALIZER令牌锄蹂,我們可以用它來添加作為應(yīng)用初始化過程的一部分調(diào)用的函數(shù)氓仲。這些函數(shù)可以返回一個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.手勢識別

Angular預(yù)先構(gòu)建了所有瀏覽器事件監(jiān)聽器敬扛,主要針對PC用戶和傳統(tǒng)的鼠標(biāo)和鍵盤事件,以及一些基本的觸摸識別事件朝抖。然而啥箭,有許多觸摸手勢可以用于更好的用戶體驗,例如平移治宣,縮放急侥,滑動和旋轉(zhuǎn)等等砌滞。Angular沒有標(biāo)配這些事件監(jiān)聽器,但是它為HammerJS提供了很好的支持坏怪,HammerJS是一個專為處理此類事件而設(shè)計的庫贝润。

<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.模板類型檢查(以及如何繞開)

在TypeScript中,我們可能沒有足夠的類型定義铝宵,例如打掘,我們可能正在使用沒有提供它們的庫,并且我們沒有可用的時間或資源來創(chuàng)建它們鹏秋。TypeScript允許我們在這種情況下使用一種特殊類型尊蚁,any它只是告訴編譯器“保持安靜,我知道我在做什么”......

作為AOT編譯過程的一部分拼岳,不僅會對TypeScript代碼執(zhí)行類型檢查枝誊,還會鍵入檢查模板。這對于檢測錯誤很有用惜纸,例如拼寫錯誤或訪問不存在的屬性叶撒。然而,與TypeScript一樣耐版,有時您可以“更好地了解”祠够,并且正在訪問某些確實存在的屬性,即使編譯器不知道它也是如此粪牲。幸運的是古瓤,通過使用$any類型轉(zhuǎn)換功能,我們有一個簡單的解決方案腺阳,例如:

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

4.Provider 的作用域

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

@NgModule({
    providers: [ApiService]
})

要么在Angular6+中:

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

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

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

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

首先绘闷,知道Injector的原理很重要橡庞。一個組件Component要查找一個service來依賴注入较坛,會先檢查自己是不是已經(jīng)注冊了Injectables 。再查父組件是否注冊了扒最。直到找到了丑勤,否則會報錯:there was no provider found。

@Self

用來限制尋找Injectable對象時吧趣,向上尋找的Injector 樹的層級法竞。它明確了查當(dāng)前說好的Component,再多一點都不查~

@Host

@Self很像强挫,也是限制向上查找的層級岔霸。它通過一些特殊情況來確認(rèn)這個Component本身的作用域:
特殊情況一:如果directive正在請求一個injectable時,就要看使用這個directiveComponent了俯渤;

特殊情況二:如果使用的是 <ng-content> 來插入的這個Component, 那么這個<ng-content> 元素也會被檢查.

@SkipSelf

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

@Optional

在我們找不到Injectable對象時, @Optional很有用. 如果找injectable對象沒找到時, 保護不拋異常.你只需要保證沒有injectable對象時,你的代碼依然能正常運行.

6.Http Interceptors

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

@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)限和安全性時呆细,路由器附帶了一個名為Guards的強大功能,它允許我們檢查用戶是否被允許訪問應(yīng)用程序中的特定頁面并阻止他們導(dǎo)航到它必要八匠。
但是絮爷,不要想著在前端做安全,因為它可以很輕松地就被偽造梨树,最好還是要做服務(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)我們有非常多的可觀察對象(observables)時,我們經(jīng)常這樣寫:

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();
    }
}

這樣寫抡四,就開始變得混亂和不可維護柜蜈,建議用這些更好的方法:

(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)使用

個人非常推薦這種方式,效果也很好指巡。在RxJS中淑履,我們有許多操作可以自動取消訂閱。例如藻雪,在某些情況下鳖谈,我們實際上只關(guān)心第一個值,或者直到滿足某個條件阔涉,在這些情況下?lián)碛袑⒆詣尤∠嗛喌膄irst和takeWhile運算符。另一個有用的是takeUntil捷绒,當(dāng)另一個emits時 我們可以使用它來取消訂閱所有的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大小暖侨,進而提升初次打開頁面的速度椭住。懶加載的模塊是在訪問某路由時,才加載相應(yīng)的JS字逗。而這里要說的是提前加載它們:

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

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

10.不可變的只讀類型

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

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

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

舉個例子,Angular使用引用相等性檢查來確定輸入是否已更改星立,如下所示:

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

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

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而言酱畅,這兩個對象仍然是相等的琳袄!

對于數(shù)組也是如此,例如纺酸,將新值推送到數(shù)組也將在相等測試中返回true窖逗,即使數(shù)組中有新項也是如此!

在這種情況下餐蔬,更新對象屬性或?qū)㈨椞砑拥綌?shù)組不會在啟用了OnPush更改檢測的組件中觸發(fā)更改檢測碎紊。

這就是不可變對象和數(shù)組的概念所在。不是更改對象中的屬性或?qū)㈨椖客扑偷綌?shù)組樊诺,而是根據(jù)原始對象創(chuàng)建新對象或數(shù)組仗考,但需要進行更改。通過進行此更改词爬,我們更改了對象引用秃嗜,允許我們的相等性檢查以了解發(fā)生了更改。

使用新的JavaScript Spread運算符,我們可以使這很簡單锅锨,例如:

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

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

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

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

有時候很難確保我們總是創(chuàng)建一個新的對象或數(shù)組叽赊,特別是在大量的數(shù)組函數(shù)可用的情況下,其中一些可以執(zhí)行必搞,有些則不可用必指。看看這里瘋狂:https//doesitmutate.xyz/顾画。

這就是TypeScript真正有用的地方取劫。我們可以告訴TypeScript在對象或數(shù)組上強制實現(xiàn)不變性,這意味著每當(dāng)我們對其進行更改而不更改其引用時研侣,我們都會遇到構(gòu)建錯誤谱邪。這樣做非常簡單!只需開始使用ReadonlyReadonlyArray類型庶诡!

// 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ù)組惦银,映射和集使用只讀類型:

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末扯俱,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子喇澡,更是在濱河造成了極大的恐慌迅栅,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件晴玖,死亡現(xiàn)場離奇詭異读存,居然都是意外死亡,警方通過查閱死者的電腦和手機呕屎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門让簿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人秀睛,你說我怎么就攤上這事尔当。” “怎么了蹂安?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵椭迎,是天一觀的道長。 經(jīng)常有香客問我田盈,道長侠碧,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任缠黍,我火速辦了婚禮,結(jié)果婚禮上药蜻,老公的妹妹穿的比我還像新娘瓷式。我一直安慰自己替饿,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布贸典。 她就那樣靜靜地躺著视卢,像睡著了一般。 火紅的嫁衣襯著肌膚如雪廊驼。 梳的紋絲不亂的頭發(fā)上据过,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天,我揣著相機與錄音妒挎,去河邊找鬼绳锅。 笑死,一個胖子當(dāng)著我的面吹牛酝掩,可吹牛的內(nèi)容都是我干的鳞芙。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼期虾,長吁一口氣:“原來是場噩夢啊……” “哼原朝!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起镶苞,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤喳坠,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后茂蚓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體壕鹉,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年煌贴,在試婚紗的時候發(fā)現(xiàn)自己被綠了御板。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡牛郑,死狀恐怖怠肋,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情淹朋,我是刑警寧澤笙各,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站础芍,受9級特大地震影響杈抢,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜仑性,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一惶楼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦歼捐、人聲如沸何陆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽贷盲。三九已至,卻和暖如春剥扣,著一層夾襖步出監(jiān)牢的瞬間巩剖,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工钠怯, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留佳魔,地道東北人。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓呻疹,卻偏偏與公主長得像吃引,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子刽锤,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 使用Angular(Angular 2 及以上版本)開發(fā)程序時镊尺,裝飾是一個核心概念。還有一個正式的TC39 提案并思,...
    OSC開源社區(qū)閱讀 1,097評論 0 11
  • 前兩天庐氮,Jigsaw七巧板上來了個issue https://github.com/rdkmaster/jigsa...
    阿踏閱讀 3,257評論 0 5
  • Foreword: 首先那要說明下,以下是我看到的一篇文章宋彼,但是原文是英文的弄砍,我只是做一個搬運工把他搬過來~主要也...
    Howie126313閱讀 10,829評論 4 41
  • 數(shù)一數(shù)日子,這個假期真的沒剩多少了输涕,雖然本來就不多音婶。我到底都做了些什么,房子沒收拾莱坎,作業(yè)也沒做衣式,每天肆意揮灑時間,...
    云去兮無歸閱讀 159評論 0 0
  • 那一天檐什, 你突然問起碴卧, 我在哪里? 你說乃正, 你好想找我去玩住册, 而我, 明知道不可能瓮具, 卻還是一臉堅定的說荧飞, 只要你...
    紅塵若夢_閱讀 142評論 3 2