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
時,就要看使用這個directive
的Component
了俯渤;
特殊情況二:如果使用的是 <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)使用Subscription
的add
方法
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)建錯誤谱邪。這樣做非常簡單!只需開始使用Readonly
和ReadonlyArray
類型庶诡!
// 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)建時為您完成末誓!