Skip to content
This repository was archived by the owner on May 3, 2024. It is now read-only.

Commit 5153afe

Browse files
author
Dmitry Efimenko
committed
fix: 🐛 not displaying error when async validator present
1 parent 1017002 commit 5153afe

File tree

6 files changed

+105
-28
lines changed

6 files changed

+105
-28
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
44

5+
## 2.0.2 (2021-12-09)
6+
7+
### Bug Fixes
8+
9+
* 🐛 not displaying error when async validator present ([684dcf5](https://github.com/ngspot/ngx-errors/commit/684dcf5114a1e2ac9c6c4e64925d6ebf262cc6ba))
10+
511
## 2.0.1 (2021-01-19)
612

713

projects/ngx-errors/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@ngspot/ngx-errors",
3-
"version": "2.0.1",
3+
"version": "2.0.2",
44
"description": "Handle error messages in Angular forms with ease",
55
"peerDependencies": {
66
"@angular/core": ">= 9.0.0",

projects/ngx-errors/src/lib/error.directive.spec.ts

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import { Component } from '@angular/core';
22
import { async } from '@angular/core/testing';
33
import {
4+
AbstractControl,
5+
AsyncValidatorFn,
46
FormControl,
57
FormGroup,
68
ReactiveFormsModule,
79
Validators,
810
} from '@angular/forms';
911
import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator';
12+
1013
import { ErrorDirective } from './error.directive';
1114
import {
1215
ErrorsConfiguration,
@@ -16,6 +19,16 @@ import {
1619
import { ErrorsDirective } from './errors.directive';
1720
import { NoParentNgxErrorsError, ValueMustBeStringError } from './ngx-errors';
1821

22+
const myAsyncValidator: AsyncValidatorFn = (c: AbstractControl) => {
23+
return new Promise((resolve) => {
24+
setTimeout(() => {
25+
if (c.value != '123') {
26+
resolve({ isNot123: true });
27+
} else resolve(null);
28+
}, 50);
29+
});
30+
};
31+
1932
@Component({})
2033
class TestHostComponent {
2134
validInitialVal = new FormControl('val', Validators.required);
@@ -24,6 +37,9 @@ class TestHostComponent {
2437
form = new FormGroup({
2538
validInitialVal: new FormControl('val', Validators.required),
2639
invalidInitialVal: new FormControl(3, Validators.min(10)),
40+
withAsyncValidator: new FormControl('', {
41+
asyncValidators: myAsyncValidator,
42+
}),
2743
});
2844

2945
submit() {}
@@ -192,7 +208,10 @@ describe('ErrorDirective', () => {
192208
});
193209

194210
describe('TEST: initial visibility', () => {
195-
let testControl: 'validInitialVal' | 'invalidInitialVal';
211+
let testControl:
212+
| 'validInitialVal'
213+
| 'invalidInitialVal'
214+
| 'withAsyncValidator';
196215

197216
When(() => {
198217
template = `
@@ -219,6 +238,37 @@ describe('ErrorDirective', () => {
219238
forStates: ['dirty', 'touched', 'touchedAndDirty', 'formIsSubmitted'],
220239
});
221240
});
241+
242+
describe('GIVEN: testControl is "withAsyncValidator"', () => {
243+
describe('initial', () => {
244+
Given(() => (testControl = 'withAsyncValidator'));
245+
ExpectErrorVisibilityForStates({
246+
expectedVisibility: false,
247+
forStates: ['dirty'],
248+
});
249+
});
250+
251+
describe('after async validator was done', () => {
252+
it('should behave...', async () => {
253+
testControl = 'withAsyncValidator';
254+
255+
template = `
256+
<form [formGroup]="form">
257+
<div ngxErrors="${testControl}">
258+
<div ngxError="isNot123"></div>
259+
</div>
260+
</form>`;
261+
createDirectiveWithConfig(showWhen);
262+
263+
const c = spectator.hostComponent.form.get(testControl)!;
264+
c.markAsTouched();
265+
266+
await wait(150);
267+
268+
expect(spectator.element).toBeVisible();
269+
});
270+
});
271+
});
222272
});
223273

224274
describe('TEST: submitting a form should display an error', () => {
@@ -358,3 +408,11 @@ describe('ErrorDirective', () => {
358408
);
359409
});
360410
});
411+
412+
function wait(t: number) {
413+
return new Promise<void>((resolve) => {
414+
setTimeout(() => {
415+
resolve();
416+
}, t);
417+
});
418+
}

projects/ngx-errors/src/lib/error.directive.ts

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,8 @@ import {
88
Optional,
99
} from '@angular/core';
1010
import { AbstractControl } from '@angular/forms';
11-
import { merge, NEVER, Observable, Subject } from 'rxjs';
12-
import {
13-
auditTime,
14-
filter,
15-
map,
16-
switchMap,
17-
takeUntil,
18-
tap,
19-
} from 'rxjs/operators';
11+
import { merge, NEVER, Observable, of, Subscription, timer } from 'rxjs';
12+
import { auditTime, filter, first, map, switchMap, tap } from 'rxjs/operators';
2013
import { ErrorsConfiguration, ShowErrorWhen } from './errors-configuration';
2114
import { ErrorsDirective } from './errors.directive';
2215
import { extractTouchedChanges } from './misc';
@@ -38,7 +31,7 @@ import { NoParentNgxErrorsError, ValueMustBeStringError } from './ngx-errors';
3831
exportAs: 'ngxError',
3932
})
4033
export class ErrorDirective implements AfterViewInit, OnDestroy {
41-
private destroy = new Subject();
34+
private subs = new Subscription();
4235

4336
@HostBinding('hidden')
4437
hidden = true;
@@ -66,9 +59,8 @@ export class ErrorDirective implements AfterViewInit, OnDestroy {
6659

6760
let touchedChanges$: Observable<boolean>;
6861

69-
this.errorsDirective.control$
62+
const sub = this.errorsDirective.control$
7063
.pipe(
71-
takeUntil(this.destroy),
7264
filter((c): c is AbstractControl => !!c),
7365
tap(() => {
7466
this.initConfig();
@@ -77,26 +69,41 @@ export class ErrorDirective implements AfterViewInit, OnDestroy {
7769
touchedChanges$ = extractTouchedChanges(control);
7870
this.calcShouldDisplay(control);
7971
}),
80-
switchMap((control) =>
81-
merge(
72+
switchMap((control) => {
73+
// https://github.com/angular/angular/issues/41519
74+
// control.statusChanges do not emit when there's async validator
75+
// ugly workaround:
76+
let asyncBugWorkaround$: Observable<any> = NEVER;
77+
if (control.asyncValidator && control.status === 'PENDING') {
78+
asyncBugWorkaround$ = timer(0, 50).pipe(
79+
switchMap(() => of(control.status)),
80+
filter((x) => x !== 'PENDING'),
81+
first()
82+
);
83+
}
84+
85+
return merge(
8286
control.valueChanges,
8387
control.statusChanges,
8488
touchedChanges$,
85-
ngSubmit$
89+
ngSubmit$,
90+
asyncBugWorkaround$
8691
).pipe(
8792
auditTime(0),
88-
takeUntil(this.destroy),
8993
map(() => control)
90-
)
91-
),
92-
tap((control) => this.calcShouldDisplay(control))
94+
);
95+
}),
96+
tap((control) => {
97+
this.calcShouldDisplay(control);
98+
})
9399
)
94100
.subscribe();
101+
102+
this.subs.add(sub);
95103
}
96104

97105
ngOnDestroy() {
98-
this.destroy.next();
99-
this.destroy.complete();
106+
this.subs.unsubscribe();
100107
}
101108

102109
private calcShouldDisplay(control: AbstractControl) {
@@ -144,7 +151,7 @@ export class ErrorDirective implements AfterViewInit, OnDestroy {
144151
return;
145152
}
146153

147-
this.showWhen = this.config ? this.config.showErrorsWhenInput : 'touched';
154+
this.showWhen = this.config?.showErrorsWhenInput ?? 'touched';
148155

149156
if (
150157
this.showWhen === 'formIsSubmitted' &&

projects/playground/src/app/lazy/lazy.component.html

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
<ng-container [ngxErrors]="form.get('firstName')!">
2+
<div ngxError="required">First name is required</div>
3+
</ng-container>
4+
15
<form [formGroup]="form">
26
<label>
37
First Name
@@ -9,9 +13,7 @@
913
Touched: {{ form.controls.firstName.touched }}
1014
</pre>
1115

12-
<div ngxErrors="firstName">
13-
<div ngxError="required" showWhen="touched">First name is required</div>
14-
</div>
16+
<hr />
1517

1618
<div formGroupName="address">
1719
<label>

projects/playground/src/app/lazy/lazy.component.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,11 @@ export class LazyComponent implements OnInit {
2626
});
2727
}
2828

29-
ngOnInit() {}
29+
ngOnInit() {
30+
setTimeout(() => {
31+
this.form.markAllAsTouched();
32+
}, 3000);
33+
}
3034

3135
submit() {
3236
console.log('submit');

0 commit comments

Comments
 (0)