From ef9f00e72f8ca9bf51cb361453c9dfbf153a2d11 Mon Sep 17 00:00:00 2001 From: Ryan Hutchison Date: Tue, 16 Jun 2020 17:13:58 -0400 Subject: [PATCH] fix: show error on async statusChanges after submit If you use control.setErrors(...) in response to an async result, the valueChanges method will not fire until the next blur/submit event. This fix includes statusChanges events after the form has been submitted. --- .../src/lib/control-error.directive.spec.ts | 27 ++++++++++++++++++- .../src/lib/control-error.directive.ts | 12 +++++---- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/projects/ngneat/error-tailor/src/lib/control-error.directive.spec.ts b/projects/ngneat/error-tailor/src/lib/control-error.directive.spec.ts index dfec9ef..91d6ba5 100644 --- a/projects/ngneat/error-tailor/src/lib/control-error.directive.spec.ts +++ b/projects/ngneat/error-tailor/src/lib/control-error.directive.spec.ts @@ -2,6 +2,7 @@ import { Component, Type } from '@angular/core'; import { FormArray, FormBuilder, FormControl, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms'; import { byPlaceholder, byText, createComponentFactory, Spectator } from '@ngneat/spectator'; import { ErrorTailorModule } from '@ngneat/error-tailor'; +import { tick, fakeAsync } from '@angular/core/testing'; function getComponentFactory(component: Type) { return createComponentFactory({ @@ -14,7 +15,8 @@ function getComponentFactory(component: Type) { useValue: { required: () => 'required error', minlength: () => 'min error', - requiredone: () => 'required one error' + requiredone: () => 'required one error', + serverError: error => error } } }) @@ -101,6 +103,29 @@ describe('ControlErrorDirective', () => { expect(spectator.query(byText(/error/))).toBeNull(); }); + + it('should show errors on async statusChanges', fakeAsync(() => { + const serverError = 'server error'; + const nameInput = spectator.query(byPlaceholder('Name')); + + typeInElementAndFocusOut(spectator, 'no error', nameInput); + + expect(spectator.query(byText(serverError))).toBeFalsy(); + + spectator.click('button'); + + setTimeout(() => { + const control = spectator.component.form.get('name'); + + control.setErrors({ serverError }); + }, 50); + + tick(50); + + spectator.detectChanges(); + + expect(spectator.query(byText(serverError))).toBeTruthy(); + })); }); describe('FormControl', () => { diff --git a/projects/ngneat/error-tailor/src/lib/control-error.directive.ts b/projects/ngneat/error-tailor/src/lib/control-error.directive.ts index 2105d65..c31196c 100644 --- a/projects/ngneat/error-tailor/src/lib/control-error.directive.ts +++ b/projects/ngneat/error-tailor/src/lib/control-error.directive.ts @@ -17,7 +17,7 @@ import { ControlErrorComponent } from './control-error.component'; import { ControlErrorAnchorDirective } from './control-error-anchor.directive'; import { EMPTY, fromEvent, merge, Observable, Subject } from 'rxjs'; import { ErrorTailorConfig, ErrorTailorConfigProvider, FORM_ERRORS } from './providers'; -import { startWith, switchMap, takeUntil } from 'rxjs/operators'; +import { distinctUntilChanged, startWith, switchMap, takeUntil } from 'rxjs/operators'; import { FormSubmitDirective } from './form-submit.directive'; import { ErrorsMap } from './types'; @@ -58,19 +58,21 @@ export class ControlErrorsDirective implements OnInit, OnDestroy { this.control = (this.controlContainer || this.ngControl).control; const isInput = this.mergedConfig.blurPredicate(this.host.nativeElement); + const statusChanges$ = this.control.statusChanges.pipe(distinctUntilChanged()); const valueChanges$ = this.control.valueChanges; - let changes$: Observable = EMPTY; + const controlChanges$ = merge(statusChanges$, valueChanges$); + let changesOnBlur$: Observable = EMPTY; if (this.controlErrorsOnBlur && isInput) { const blur$ = fromEvent(this.host.nativeElement, 'focusout'); // blurFirstThanUponChange - changes$ = blur$.pipe(switchMap(() => valueChanges$.pipe(startWith(true)))); + changesOnBlur$ = blur$.pipe(switchMap(() => valueChanges$.pipe(startWith(true)))); } // submitFirstThanUponChanges - const submit$ = this.submit$.pipe(switchMap(() => valueChanges$.pipe(startWith(true)))); + const changesOnSubmit$ = this.submit$.pipe(switchMap(() => controlChanges$.pipe(startWith(true)))); - merge(submit$, changes$) + merge(changesOnSubmit$, changesOnBlur$) .pipe(takeUntil(this.destroy)) .subscribe(() => this.valueChanges()); }