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è)directive的Component了;
特殊情況二:如果使用的是 <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)使用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)使用
個(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)始使用Readonly和ReadonlyArray類(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í)為您完成!