Background: The formControl of the current input box is set with an asynchronous validator, which will request the background according to the current value to determine whether it exists in the database.
Original async validator:
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));
};
}
But after testing, it was found that the asynchronous validator was triggered too frequently. Every time you enter a letter in the input box, a request will be made to the background, which is not conducive to saving resources.
Anti-shake throttling
This related operation is called stabilization and throttling. What is anti-shake and throttling? What's the difference?
Essentially a means of optimizing high-frequently executed code.
For example, when events such as mouse clicks and keyboard input are triggered in the browser, the callback functions bound to the events will be called frequently, which affects the utilization of resources to a certain extent.
For optimization, we need debounce and throttle to reduce the frequency of calls.
definition:
Anti- shake : The event is executed after n seconds, if it is repeatedly triggered within n seconds, the time will be re-timed
Throttling : only run once within n seconds, if it is triggered repeatedly within n seconds, only once will take effect
To illustrate with an example:
When taking the subway, when passing the gate, the door will be closed 3 seconds after each person enters, waiting for the next person to enter.
After the gate is opened, wait for 3 seconds. If someone passes through the gate, wait for 3 seconds to re-time until no one passes after 3 seconds. This is anti-shake .
After the gate is opened, it will be closed on time every 3 seconds, and the interval will be executed. This is throttling .
Code:
The anti-shake operation is exactly what we need.
Looking for the code implementation of anti-shake in the asynchronous validator, I happened to see the article of senior liyiheng:
https://segmentfault.com/a/1190000021157275 , so I made a reference.
Here are just the steps to illustrate how the formContorl asynchronous validator in angular is debounced:
1. Create (rewrite) async validators
vehicleBrandNameNotExist(): AsyncValidatorFn {
return (control: AbstractControl): Observable<ValidationErrors | null> => {
if (control.value === '') {
return of(null);
}
return control.valueChanges.pipe(
// 防抖时间,单位毫秒
debounceTime(1000),
// 过滤掉重复的元素
distinctUntilChanged(),
// 调用服务, 获取结果
switchMap(value => this.vehicleBrandService.existByName(value)),
// 对结果进行处理,null表示正确,对象表示错误
map((exists: boolean) => (exists ? {vehicleBrandNameExist: true} : null)),
// 每次验证的结果是唯一的,截断流
first()
)
};
}
- Add async validators
let formControl = new FormControl('', [], asyncValidate.vehicleBrandNameNotExist());
After that, we can bind the fromControl on the relevant label in the v layer.
Confuse
The related operations are over here and can be used normally.
But there are still some doubts after the rewrite.
The original version was used like this:
return this.vehicleBrandService.existByName(...)
After rewriting it is used like this:
return control.valueChanges.pipe(...
After rewriting, valueChanges is used, that is, an observable is generated, which makes it emit an event whenever the value of the control changes.
So, after each call to the asynchronous validator, we use valueChanges every time, is the observable the same every time?
So I tested:
Principle: call the asynchronous validator multiple times, and cache the ovservable, if it is not the same, output "not equal"
Test result: As shown in the figure, the output is not equal when it is initialized for the first time, because the first observable is undefined. After it has a value, the asynchronous validator is called multiple times to find that the observable is always the same.
Use of first()
I didn't understand the use of first before, but after reading the senior's article, I realized that first() avoids returning values in this way multiple times.
So the observable we generated has been in the pending state, and we need to use first to make it return the first value.
return control.valueChanges.pipe(
first()
)
unit test
A good function has a good unit test.
1 it('should create an instance', async () => {
2 expect(asyncValidate).toBeTruthy();
3 let formControl = new FormControl('', [], asyncValidate.vehicleBrandNameNotExist());
4 formControl.setValue('重复车辆品牌');
5 // 等待防抖结束
6 await new Promise(resolve => setTimeout(resolve, 1000));
7 getTestScheduler().flush();
8 expect(formControl.errors.vehicleBrandNameExist).toBeTrue();
...
}));
The original unit test I wrote said something like this,
Waiting for the anti-shake to end I used await new Promise and setTimeout. When executing line 8, let the thread wait for 1 second.
After the teacher corrected it, it was found that this was not good. If a test needs to wait an hour, then our execution time will take 1 hour, which is obviously unrealistic.
So fakeAsync is used here;
fakeAsync;
fakeAsync, literally fake asynchronous, is actually synchronous.
Simulate the asynchronous passage of time using tick().
Official test code:
Following the test code:
Before and after tick(), I printed new Date(), which is the time at that time. What is the result?
You can see that the first one prints 17:19:30, which is the time of the test at that time.
But after tick(10000000), the printed time is 20:06:10, reaching a future time.
Also, these two statements are printed almost at the same time, that is, the unit test doesn't make us really wait 10000000ms.
So after testing, we can use tick(1000) and fakeAsync to simulate the end of the anti-shake time.
it('should create an instance', fakeAsync( () => {
expect(asyncValidate).toBeTruthy();
let formControl = new FormControl('', [], asyncValidate.vehicleBrandNameNotExist());
formControl.setValue('重复车辆品牌');
// 等待防抖结束
tick(1000);
getTestScheduler().flush();
expect(formControl.errors.vehicleBrandNameExist).toBeTrue();
}));
off topic
I also encountered an error when writing the background:
It says that my color is not set to a default value,
But when I go back, it is obvious that it has been set.
Hit a lot of breakpoints and found no problem.
Later, I went to the database and looked at it. Good guy, how come there are two, one is colour and the other is color.
After looking at the code submitted before, I found that color was used before, and then it was changed to colour.
But my jpa hibernate is set to update, so the database is updated accordingly, so the last field was not deleted, which led to the database having two fields. Then delete one of them and it will be fine.
jpa:
hibernate:
ddl-auto: update
Replenish
Later, Google found a relatively concise and easy-to-understand method:
Instead of calling operators like first(), just use the return value of timer() as an observable.
The role of time is here:
https://rxjs-cn.github.io/learn-rxjs-operators/operators/creation/timer.html
Simply put, it emits a value when the timer expires.
This principle guesses that when the timer has not ended and the asynchronous validator is repeatedly called, the form ignores the timer and focuses on the new one.
Of course, it's just a guess, and there is a chance to add it. After testing, the anti-shake function is normal.
export class VehicleBrandAsyncValidator {
/**
* 防抖时间
*/
debounceTime = 1000;
constructor(private vehicleBrandService: VehicleBrandService) { }
/**
* 验证方法,车辆品牌名称
*/
vehicleBrandNameNotExist(): AsyncValidatorFn {
return (control: AbstractControl): Observable<ValidationErrors | null> => {
if (control.value === '') {
return of(null);
}
return timer(this.debounceTime).pipe(
// 调用服务, 获取结果
switchMap(() => this.vehicleBrandService.existByName(control.value)),
// 对结果进行处理,null表示正确,对象表示错误
map((exists: boolean) => (exists ? {vehicleBrandNameExist: true} : null)),
)
};
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。