angular異步驗(yàn)證器防抖實(shí)例詳解
背景:
當(dāng)前輸入框的formControl設(shè)置了異步驗(yàn)證器,會根據(jù)當(dāng)前的值進(jìn)行請求后臺,判斷數(shù)據(jù)庫中是否存在。
原版異步驗(yàn)證器:
vehicleBrandNameNotExist(): AsyncValidatorFn { return (control: AbstractControl): Observable<ValidationErrors | null> => { if (control.value === '') { return of(null); } return this.vehicleBrandService.existByName(control.value).pipe(map(exists => exists ? {vehicleBrandNameExist: true} : null)); }; }
但是測試下來發(fā)現(xiàn),該異步驗(yàn)證器觸發(fā)的太頻繁了。輸入框每輸入一個字母都會對后臺進(jìn)行請求,不利于節(jié)省資源。
防抖節(jié)流
這個相關(guān)的操作叫做防抖和節(jié)流。什么是防抖和節(jié)流?有什么區(qū)別?
本質(zhì)上是一種優(yōu)化高頻率執(zhí)行代碼的一種手段。
比如瀏覽器的鼠標(biāo)點(diǎn)擊,鍵盤輸入等事件觸發(fā)時,會高頻率地調(diào)用綁定在事件上的回調(diào)函數(shù),一定程度上影響著資源的利用。
為了優(yōu)化,我們需要 防抖(debounce) 和 節(jié)流(throttle) 的方式來減少調(diào)用頻率。
定義:
防抖: n 秒后在執(zhí)行該事件,若在 n 秒內(nèi)被重復(fù)觸發(fā),則重新計時
節(jié)流: n 秒內(nèi)只運(yùn)行一次,若在 n 秒內(nèi)重復(fù)觸發(fā),只有一次生效
舉個例子來說明:
乘坐地鐵,過閘機(jī)時,每個人進(jìn)入后3秒后門關(guān)閉,等待下一個人進(jìn)入。
閘機(jī)開之后,等待3秒,如果中又有人通過,3秒等待重新計時,直到3秒后沒人通過后關(guān)閉,這是防抖。
閘機(jī)開之后,每3秒后準(zhǔn)時關(guān)閉一次,間隔時間執(zhí)行,這是節(jié)流
代碼實(shí)現(xiàn):
防抖操作恰好符合我們的需求。
找異步驗(yàn)證器中防抖的代碼實(shí)現(xiàn)中恰好看到了liyiheng學(xué)長的文章:
http://www.dbjr.com.cn/article/175497.htm,于是便參考了一下。
這里僅是說明angular中formContorl異步驗(yàn)證器如何防抖的步驟:
1.創(chuàng)建(改寫)異步驗(yàn)證器
vehicleBrandNameNotExist(): AsyncValidatorFn { return (control: AbstractControl): Observable<ValidationErrors | null> => { if (control.value === '') { return of(null); } return control.valueChanges.pipe( // 防抖時間,單位毫秒 debounceTime(1000), // 過濾掉重復(fù)的元素 distinctUntilChanged(), // 調(diào)用服務(wù), 獲取結(jié)果 switchMap(value => this.vehicleBrandService.existByName(value)), // 對結(jié)果進(jìn)行處理,null表示正確,對象表示錯誤 map((exists: boolean) => (exists ? {vehicleBrandNameExist: true} : null)), // 每次驗(yàn)證的結(jié)果是唯一的,截斷流 first() ) }; }
- 添加異步驗(yàn)證器
let formControl = new FormControl('', [], asyncValidate.vehicleBrandNameNotExist());
之后我們在v層在相關(guān)的標(biāo)簽上綁定該fromControl就可以了。
疑惑
相關(guān)操作到這里就結(jié)束了,能夠正常使用了。
但是改寫之后還有些疑惑。
原來的版本是這么使用的:
return this.vehicleBrandService.existByName(...)
改寫后是這么使用的:
return control.valueChanges.pipe(...
改寫后使用了valueChanges,也就是產(chǎn)生了一個observable,它使得每當(dāng)控件的值在更改時,它都會發(fā)出一個事件。
那么,每次調(diào)用異步驗(yàn)證器之后,我們每次都用valueChanges,每次的observable是不是同一個?
于是我進(jìn)行了測試:
原理:多次調(diào)用異步驗(yàn)證器,并緩存ovservable,如果不相同則輸出 “不相等”
測試結(jié)果:如圖,只是在第一次初始化的時候輸出了不相等,因?yàn)榈谝淮蝟bservable為undefined, 在有值之后,多次調(diào)用異步驗(yàn)證器發(fā)現(xiàn)observabel始終是同一個
first()的使用
之前也不理解first的使用,看學(xué)長的文章之后才明白,first()來避免多次地這樣返回值。
所以我們產(chǎn)生的observable一直處于pending狀態(tài),需要用first讓它返回第一個值就好。
return control.valueChanges.pipe( first() )
單元測試
一個好的功能要有一個好的單元測試。
1 it('should create an instance', async () => { 2 expect(asyncValidate).toBeTruthy(); 3 let formControl = new FormControl('', [], asyncValidate.vehicleBrandNameNotExist()); 4 formControl.setValue('重復(fù)車輛品牌'); 5 // 等待防抖結(jié)束 6 await new Promise(resolve => setTimeout(resolve, 1000)); 7 getTestScheduler().flush(); 8 expect(formControl.errors.vehicleBrandNameExist).toBeTrue(); ... }));
原來的時候我寫的單元測試說這樣的,
等待防抖結(jié)束我用了await new Promise 以及setTimeout。執(zhí)行到第8行的時候,讓線程等待1秒。
經(jīng)過老師指正之后,發(fā)現(xiàn)這樣并不好。假如某個測試需要等待一個小時,那么我們的執(zhí)行時間就需要1個小時,這顯然是不現(xiàn)實(shí)的。
所以這里用到了fakeAsync;
fakeAsync;
fakeAsync,字面上就是假異步,實(shí)際上還是同步進(jìn)行的。
使用tick()模擬時間的異步流逝。
官方測試代碼:
仿照測試代碼:
我在tick()前后,打印了new Date(),也就是當(dāng)時的時間,結(jié)果是什么呢?
可以看到第一個打印了17:19:30,也就是當(dāng)時測試的時間。
但是在tick(10000000)后,打印的時間是20:06:10, 達(dá)到了一個未來的時間。
并且,這兩條語句幾乎是同時打印的,也就是說,單元測試并沒有讓我們真的等待10000000ms。
所以經(jīng)過測試時候我們就可以使用tick(1000)和fakeAsync模擬防抖時間結(jié)束了。
it('should create an instance', fakeAsync( () => { expect(asyncValidate).toBeTruthy(); let formControl = new FormControl('', [], asyncValidate.vehicleBrandNameNotExist()); formControl.setValue('重復(fù)車輛品牌'); // 等待防抖結(jié)束 tick(1000); getTestScheduler().flush(); expect(formControl.errors.vehicleBrandNameExist).toBeTrue(); }));
題外
寫后臺的時候還遇到了一個錯誤:
它說我color沒有設(shè)置默認(rèn)值,但是回去一看明明已經(jīng)設(shè)置了。
打了很多斷點(diǎn)都沒發(fā)現(xiàn)問題。
后來到數(shù)據(jù)庫一看,好家伙,怎么有兩個,一個是colour,一個是color.
之后翻看之前提交的代碼,發(fā)現(xiàn)是之前用的是color,后面換成了colour。
但是我jpa hibernate設(shè)置的是update,所以數(shù)據(jù)庫對應(yīng)執(zhí)行的是更新,所以上次的字段并沒有刪除,這才導(dǎo)致了數(shù)據(jù)庫有兩個字段。之后刪除其中一個了就沒事了。
jpa: hibernate: ddl-auto: update
補(bǔ)充
后面谷歌之后發(fā)現(xiàn)了一種比較簡潔也好理解的方法:
不用調(diào)用first()之類的操作符, 把timer()的返回值作為一個observable就可以了。
time的作用在這里:
https://rxjs-cn.github.io/lea...
簡單來說就是當(dāng) timer 結(jié)束時發(fā)出一個值。
這個原理猜測可能是當(dāng)timer還沒有結(jié)束并重復(fù)調(diào)用異步驗(yàn)證器時,表單就不管這個timer了,轉(zhuǎn)而關(guān)注新的。
當(dāng)然只是猜測,有機(jī)會再補(bǔ)充,經(jīng)過測試防抖功能是正常的。
export class VehicleBrandAsyncValidator { /** * 防抖時間 */ debounceTime = 1000; constructor(private vehicleBrandService: VehicleBrandService) { } /** * 驗(yàn)證方法,車輛品牌名稱 */ vehicleBrandNameNotExist(): AsyncValidatorFn { return (control: AbstractControl): Observable<ValidationErrors | null> => { if (control.value === '') { return of(null); } return timer(this.debounceTime).pipe( // 調(diào)用服務(wù), 獲取結(jié)果 switchMap(() => this.vehicleBrandService.existByName(control.value)), // 對結(jié)果進(jìn)行處理,null表示正確,對象表示錯誤 map((exists: boolean) => (exists ? {vehicleBrandNameExist: true} : null)), ) }; } }
總結(jié)
到此這篇關(guān)于angular異步驗(yàn)證器防抖的文章就介紹到這了,更多相關(guān)angular異步驗(yàn)證器防抖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
AngularJS基礎(chǔ) ng-mouseleave 指令詳解
本文主要介紹AngularJS ng-mouseleave 指令,這里幫大家整理了ng-mouseleave指令的詳細(xì)資料,并附有代碼示例,有需要的小伙伴可以參考下2016-08-08Angular.js項(xiàng)目中使用gulp實(shí)現(xiàn)自動化構(gòu)建以及壓縮打包詳解
基于流的前端自動化構(gòu)建工具,利用gulp可以提高前端開發(fā)效率,特別是在前后端分離的項(xiàng)目中。下面這篇文章主要給大家介紹了關(guān)于在Angular.js項(xiàng)目中使用gulp實(shí)現(xiàn)自動化構(gòu)建以及壓縮打包的相關(guān)資料,需要的朋友可以參考下。2017-07-07Angular中ng-options下拉數(shù)據(jù)默認(rèn)值的設(shè)定方法
本篇文章主要介紹了Angular中ng-options下拉數(shù)據(jù)默認(rèn)值的設(shè)定方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-06-06AngularJs unit-testing(單元測試)詳解
本文主要介紹AngularJs unit-testing(單元測試)的內(nèi)容,這里整理了單元測試的知識,及示例代碼,有興趣的朋友可以參考下2016-09-09使用Angular9和TypeScript開發(fā)RPG游戲的方法
這篇文章主要介紹了使用Angular9和TypeScript開發(fā)RPG游戲的方法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-03-03angularJs select綁定的model取不到值的解決方法
今天小編就為大家分享一篇angularJs select綁定的model取不到值的解決方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-10-10