angular / angular

Deliver web apps with confidence 🚀

Home Page:https://angular.dev

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Async validators can sometimes cause form validity status to be stuck as 'PENDING'

vincent-vieira opened this issue · comments

I'm submitting a ... (check one with "x")

[x] bug report => search github for a similar issue or PR before submitting
[ ] feature request
[ ] support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Current behavior
Async validation can sometimes hang form validity status. Returned Promises and Observables are sometimes "ignored" and thus, the form is never marked as valid.

Expected behavior
The underlying async objects would have to be resolved properly and form validity status must update properly.

Minimal reproduction of the problem with instructions
Example Plunkr:
http://plnkr.co/edit/nGiwOkCHngVTZKFrJ501?p=preview

Steps to reproduce :

  • I haven't fully investigated the issue yet, but creating some AsyncValidatorFn returning either a Promise or an Observable will do. Unfortunately, there is no "magic" async function making the test crash.
  • Just type something in the input box, triggering validators and you'll see that the HTTP call fetching the JSON error object works, that the observable .do() method is called, logging the result in the console, but the returned Observable is never resolved by Angular, and that the form status is stuck to 'PENDING'.

What is the motivation / use case for changing the behavior?
I heavily use Redux and Observables inside my projects and thus, extensively use async validators too. Having this kind of issues is somehow very unconfortable when working with async helpers.

Please tell us about your environment:

  • Operating system: Ubuntu 16.04

  • IDE: Webstorm 2016.3.1

  • package manager: NPM version 3.10.8

  • HTTP server: Express v.4.14.0

  • Angular version: Tested on 2.0.0 (locally) and 2.0.4 (on the Plunkr)

  • Browser: Firefox 50.0.1/Chrome 55.0.2883.75

  • Language: TypeScript 2

  • Node (for AoT issues): node --version = v6.9.1

I'm having exactly the same issue!

When the form is a 'new' object, there's no problem as the user types anything and validation kicks in.
However, on edit mode (using the same form), when inserting the value from db at form init, all fields with AsyncValidators are stuck on Pending status.

Drilling down a bit I found something very odd.
AbstractControl.prototype.updateValueAndValidity runs this._runValidator() (sync) first.
If I define my AsyncValidator as:
this.name = new FormControl(values.name, Validators.composeAsync([this.svc.validate)]));

the updateValueAndValidity runs as the Synchronous validator, not as Async. The control itself also does not receive the AsyncValidator.

if I add the validation using
this.name = new FormControl(values.name); this.name.setAsyncValidators(this.svc.validate);

then the AsyncValidator gets assigned and the updateValueAndValidity runs the this._runAsyncValidator(emitEvent) but the initial validation gets stuck on Pending status even though the return of the validation is valid.

Is there any fix or workaround for this? I'm experiencing the same condition now. I have an async validator that is hitting an api to check for the existence of a name and returning properly but the form is never registering the observable as complete and is stuck in pending.

@blacknight811 This has happened to me when I forget to return a response from the observable. So if you are generating a new observable on each validation check, and let's say have a filter that checks whether there is anything in control.value, if the filter conditional fails and doesn't pass to your api, then the control will be marked as pending because that call to filter never finished. Even if another instance passes, that original call will still have never completed as each call to the validator is unique. So make sure the observable is marked as complete each time you hit the validator, even if there is no value.

I encountered the same issue in my Angular 4.1 app. After @minuz already observed a similar pattern, I think this could be related to validation of a FormGroup actually used in a template versus used independently. In the latter case my FormGroup with async validators always stays in "PENDING" state. It seems like Angular does not make the async call in order to save network requests, as it thinks, that the FormGroup isn't actually in use.

If that's the case, I would request an option to force async Validations on AbstractControls. Validation of a form which is currently not rendered is a strong use case for us.

Has anyone identified a solution yet? I feel fairly certain that take(1) should cause the complete to get called yet the form and the formcontrol seem to remain stuck in Pending.

    return this.accountService
        .doesEmailAddressExist(email)
        .takeUntil(this.changed$)
        .take(1)
        .debounceTime(500)
        .flatMap(emailinUse => {
            if (emailinUse) {
                return Observable.of({ inUse: true });
            } else {
                return Observable.of(null);
            }
        }); 

edit: I was using takeUntil(this.changed$) as a workaround for the inability to use debounceTime(500) within an async validator as recommended by #6895.

This lead the problem at hand where the original observable isn't being completed and the form is still listed as 'PENDING'.

If I remove this added complication the form resolves. I suppose I need to find another workaround for debounceTime().

I believe I found where the bug is:

  private _runAsyncValidator(emitEvent?: boolean): void {
    if (this.asyncValidator) {
      this._status = PENDING;
      const obs = toObservable(this.asyncValidator(this));
      this._asyncValidationSubscription =
          obs.subscribe((errors: ValidationErrors | null) => this.setErrors(errors, {emitEvent}));
    }
  }

the obs.subscribe does not assign the result of the observable into this._status causing the result to be always on PENDING.

I'll try to do my first PR with this :D

I don't know, but maybe this is related to #14542

@minuz Did you manage to get this fixed eventually? I am struggling with the same issue. Workaround with .take(1) works but debouncing does not. This seems like an easy fix but I was unable to run angular test suite locally otherwise I'd attempt to make a PR too.

i wonder if this is bug? coz im curious, is this serious? issue created since 2 Dec 2016 and no fix.
Lost time to find why this happen ... ?!?!

thanks

Ran into this with an async validator too, with switchMap and debounce and take(1). Any thoughts on working around this? Seems like a common use case to me.

NOTE: I figured out a workaround for the debounceTime - just use debounce instead:

export function createAsyncDescriptionValidator(taskService: TaskService): AsyncValidatorFn {

  // NOTE : the take(1) and debounceTime currently wreak havoc on the pending state of the form.
  // This is a great academic exercise but you'd have to remove debounceTime in order to get it
  // to work properly. See https://github.com/angular/angular/issues/13200
  return (control: FormControl): Observable<{ invalidPattern: string} | null> => {
    // on initial load, forms sometimes do not have valueChanges available
    // until the initial edits... assumes that incoming data is valid
    if (!control.valueChanges) {
      return Observable.of(null);
    } else {
      return control.valueChanges
        .debounce(() => Observable.interval(1000))
        .take(1)
        .switchMap(value => taskService.validateDescription(value))
        .map(value => value.valid ? null : {invalidPattern: 'Your task cannot contain the word: poison!'});
    }
  };
}

(from one of my class exercises)

Using debounce over debounceTime seems to work fine. It doesn't unnecessarily make the form pending and it clears as soon as it is done. Guarded my button with:

  <button class="btn btn-primary" 
       [disabled]="taskForm.invalid || (!taskForm.pristine && taskForm.pending)">Save</button>

based on @krimple sample code, I implemented mine like so:

private _validateNameNotTaken(control: AbstractControl) {
  if (!control.valueChanges || control.pristine) {
    return Observable.of( null );
  }
  else {
    return control.valueChanges
      .debounceTime( 300 )
      .distinctUntilChanged()
      .take( 1 )
      .switchMap( value => this.unitService.isNameTaken(value) )
      .do( () => control.markAsTouched() )
      .map( isTaken => isTaken ? { nameTaken: true } : null );
  }
}

mine has a few extras, like distinctUntilChanged(), and a hack at the end .markAsTouched() because I wanted to show the error message as it changes directly from the first time, and not only when the focus is out of the input.

but anyways, using debounce instead of debounceTime didn't work for me, so I added || control.pristine in the first If statement. Since I'll be assuming that the data that was previously saved is valid, then there's no need to check when it's just freshly loaded.

This won't work if you're loading invalid data from somewhere else.

Nice touch (the markAsTouched is a great idea)! I wonder why I had to use debounce instead of debounceTime. I'll try something like yours and see if that was a red herring.

I just noticed that .distinctUntilChanged is not working in this case, no idea why, will investigate that later.

the obs.subscribe does not assign the result of the observable into this._status causing the result to be always on PENDING.

@minuz I am seeing the same thing, my control is stuck in PENDING status forever. I was not able to find any line of code that changes that and I was curious if you made any progress on your side.

The issue for me is that the value changes are fired before the async validation has completed and thus the last time ValueChanges was fired it has a Pending Status.

My workaround was to just combine the value changes with another observable that I fire once validation was completed.

Just make sure to use a BehaviourSubject for asyncValidationComplete$.

CombineLatest emits an item whenever any of the source Observables emits an item (so long as each of the source Observables has emitted at least one item).

 return control.valueChanges
        .debounceTime(500)
        .take(1)
        .switchMap(() => {
          return this.userService.usernameExists(username, this.user ? this.user.id : null);
        })
        .map(exists => {
          return exists ? {'forbiddenName': {value: control.value}} : null;
        })
        .finally(() => this.asyncValidationComplete$.next(true));
this.userForm.valueChanges
      .combineLatest(this.asyncValidationComplete$)

I'm not sure to what extent this may be related, but in my case, there are situations where I retain common validation responses from the API in cache for a set lifetime. When the validation request is subsequently called, I determine if I have an available rule in cache exists that isn't expired, and return the cached version declared as BehaviorSubject, returning asObservable instead of hitting the API again. Each call to cache returns a new instance of BehaviorSubject.

The async validator successfully processes .map() and returns the appropriate result from cache, but the state of the AbstractControl retains it's PENDING status.

This is happening because the observable never completes, so Angular does not know when to change the form status. So remember your observable must to complete.

You can accomplish this in many ways, for example, you can call the first() method, or if you are creating your own observable, you can call the complete method on the observer.

return control.valueChanges
.debounceTime(500)
.take(1)
.switchMap(() => {
return this.userService.usernameExists(username, this.user ? this.user.id : null);
})
.map(exists => {
return exists ? {'forbiddenName': {value: control.value}} : null;
})
.first()

@vamlumber I will seriously love you long time. This has been killing me for months. Thank you!!!

I think the issue should be closed now

I'm not sure if completing an observable is the right solution here. Does that mean that validation will update only once? What if the observable stream is coming from web sockets and I'd like to update validation every time a new value comes from the server - not just once.

I really struggled with this and subscribing to valueChanges wasn't working with vamlumbers example.

I ended up with a validation function like this:

	validate = (control: AbstractControl): Observable<ValidationErrors> => {
		return Observable.timer(500).pipe(
			switchMap(() => this.nameExists(control.value)),
			take(1),
			map(isDuplicate => this.stateToErrorMessage(isDuplicate)),
			tap(() => control.markAsTouched()),
			first()
		);
	};

Returning an observable with the control.value prop.

This approach works for me:

return (control: AbstractControl): Observable<ValidationErrors | null> => {
    return control.valueChanges
        .debounceTime(500)
        .switchMap(() => auth.checkUsernameTaken(control.value))
        .map(taken => taken ? {usernameTaken: true} : null)
        .catch(err => Observable.of(null))
        .first()
  };    

i resolved form pending issue when add .catch() error line,
since my API return error if username not found, in this case form status stucks, because it doesn't enter .map() and doesn't return anything.
And according to rxjs docs, you need to return observable from catch().

Hello there,

So in RxJs 6 first() is deprecated. Should take(1) be used instead?

@bloArribas I can't find any source for it being deprecated. Care to indulge?

My mistake @krokofant, it has not been deprecated. However, for the record, it has changed. https://github.com/ReactiveX/rxjs/blob/master/MIGRATION.md

It still works for this example.

I updated to Angular v.6.0.0, and rxjs 6.1.0, I'll share some hurdles I found after the update and how I fixed them.

I have a formGroup that is async validated, through a request to backend. The formGroup has 2 inputs below. The validation is intended to be performed only when both inputs have a value.

After the upgrade to the newer versions of Angular and rxjs the validation would run correctly, but the component would not run the change detection. I have functions called from ngClass directives on the inputs containers, to add the 'has-success' or 'has-error' bootrstrap classes.
After the update the classes on the inputs would not be updated, even though I could assess that the values were correct.

Finally, I fixed it by forcing the change detection after the validation. I think passing the component change detector to the validator is an anti-pattern, but it works.

This is the validator:


export class ServerFQDNValidator {

  static validateAsync (dataService: DataService, store: Store<State>, emitServerDataRequest: boolean, componentChangeDetector: ChangeDetectorRef): AsyncValidatorFn {

    return (control: FormControl): Observable<{ [key: string]: any } | null> => {
      let hostname;
      let domainId;

      store.select(fromData.selectServerFQDN)
        .subscribe((fqdn: [string, string]) => {
          [hostname, domainId] = fqdn;
        });

      const hasChanged = hostname !== control.get('hostname').value || domainId !== control.get('domain').value;

      if (hasChanged && emitServerDataRequest) {
        return control.valueChanges.pipe(
          debounceTime(400),
          first(),
          switchMap((value: { hostname: string, domain: number }) =>
            dataService.validateDecommissionFQDN(value.hostname, value.domain)
          ),
          map(res => {
            if (res) {
              store.dispatch(new LoadServerData({ serverName: control.get('hostname').value, domainId: control.get('domain').value }));
              return null;
            }
            return of({ 'fqdnNotFound': { value: control.value } });
          }),
          catchError((error): any => {
            store.dispatch(new ServerFQDNValidationError({ validationResult: false }));
            return of({ 'fqdnNotFound': { value: control.value } });
          }),
          tap(() => {
            control.get('hostname').markAsTouched();
            control.get('domain').markAsTouched();
            componentChangeDetector.markForCheck();
          })
        );
      } else {
        return of(control.errors);
      }
    };
  }
}

Hi all,

All the solutions on this thread doesn't works when the component has the ChangeDetectionStrategy.OnPush and the unique solution that works is the last one, with the changeDetector as a param.

Any more elegant/correct solution?

There are several things to note here:

  1. The main use case where developers would experience this issue is "edit" screens where the form is populated with data and initial validation is run.
  2. statusChanges are not emitted only when async validation is run during the initialization of the view (the formControlName and formGroup directives)
  3. Calling updateValueAndValidity() before formControlName and formGroup directives have been initialized will not fix the issue.
  4. Calling updateValueAndValidity() on a FormGroup will not automatically invoke the asyncValidator of a child FormControl (I guess this is by design).

So to me it seems that the most straightforward solution is to delay the first (only?) population of the form until the view has been initialized and form controls have been bound. A simple setTimeout of 0 should do the trick and will be called one time only (as opposed to other workarounds). Given we obtain the initial form value from an input, here is a simple workaround:

ngOnChanges(changes: SimpleChanges) {
  if (changes['initialFormValue']) {
    const delay = changes['initialFormValue'].isFirstChange() ? timer(0).toPromise() : Promise.resolve(null);

    delay.then(() => this.populateForm());
  }
}

@revov Thanks for the analysis!

Honest question, is anyone else surprised that Angular moved up another full major version without mentioning this?
I am not trying to bash. I love Angular, and could not have more respect.

I just never would have imagined an issue as central to building web apps as default async form validation would go two+ years and a couple full major version changes without any mention here, let alone a fix.

Because the Angular team is so top shelf, this makes me feel like I'm missing something and this issue isn't as important as it feels to me. Anyone have any insight on my confusion?

I am having the same exact issue

Are there any updates? I'm having the same issue...

Finally getting back to this. I created a codesandbox to test Angular 6 against very simple async validators and with switchMap I could not get it to fail. Can we fork this until we get a failure?

https://codesandbox.io/s/z3253zykmm

The thing that you absolutely must use is switchMap so that the validator, when subscribing to validation output, switches to a terminal observable (see both branches use 'of'). I have a feeling anything other than this will be the problem where it gets stuck.

And looking at the above, seems like we have a few ways to trigger the problem, but didn't research yet that far. Just wanted a baseline in which to start experiments. If anything throws an error and you don't somehow trap it, it will cause problems if you don't give the observable feed a "way out".

SwitchMap still doesn't fix it when using OnPush as change detection strategy.
Here is a repro:
https://stackblitz.com/edit/angular-4chvht-rsmkc6

Alrighty. Here's using change detection strategy of onPush with the switchMap. It is stable, so I am wondering if there's a path through the stream where something doesn't call the change detector ref and reconcile.

https://codesandbox.io/s/m430q31xpx

btw had to do some trickery to not lose the 'this' - externalized the validate function and created it with a creation function when setting up the validator, passed it the detector ref when 'this' was stable.

Always check your 'this', 'self', etc as a test within the observable. Pipe to a tap (or do a do()) to debug what the values you think are defined actually are. I know that sounds obvious but for anyone less experienced reading the thread, I find those incredibly useful debugging techniques.

@dobrevad - that's your problem. When you set the detector strategy to onPush, any change made internally to the state of the object programmatically will need a call to a ChangeDetectorRef. See my sample. Once you go to OnPush, only replaced inputs or observable 'next' methods on inputs will trigger changes automatically. Internal observables don't count.

Edit: This is with Angular v6.1.0, rxjs v6.0.0.

For my situation, where my component was using OnPush change detection strategy, I used ChangeDetectorRef with statusChanges to trigger an update:

@Component({
  selector: 'ws-create-form',
  templateUrl: './create-form.component.html',
  styleUrls: ['./create-form.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class CreateFormComponent implements OnInit, OnDestroy {
  @Input() form: FormGroup;
  destroy: Subject<boolean> = new Subject<boolean>();

  constructor(private cdr: ChangeDetectorRef) {}

  ngOnInit() {
    this.form.statusChanges
      .pipe(
        takeUntil(this.destroy),
        distinctUntilChanged()
      )
      .subscribe(() => this.cdr.markForCheck());
  }

  ngOnDestroy() {
    this.destroy.next(true);
    this.destroy.complete();
  }
}

I was using OnPush, but my form.statusChanges wasn't even emitting so change detection wouldn't have helped. I have been using take(1) to complete the observable (which sucks because I think some fields should be able to go invalid asynchronously without the check having to be triggered by user input), but I was still having this problem. I also tried the setTimeout idea, but it didn't work for me.

While debugging, I would subscribe in the console to form.statusChanges and also set an interval to log form.status to the console, and I consistently got PENDING to be the last thing emitted by form.statusChanges, and yet the interval logging form.status would log PENDING and then VALID after the observable completed. I knew it was completing because I had .finally(() => console.log('Validation observable completed')).

My best (but still ugly imo) solution was to define a subject

statusChange$ = new Subject();

and alter the finally to

.finally(() => this.statusChange$.next(null))

Then I had this:

canSave$ = Observable.combineLatest(
    this.form.statusChanges,
    this.form.valueChanges,
    this.statusChange$,
	() => this.form.status === 'VALID'
)

used in the template like this: [disabled]="!(canSave$ | async)"

A quick and dirty workaround for this:

ngOnInit() {
    this.form = ... init form ...
    
    setTimeout(() => {
        const control = this.form.get("slug"); // control which has async validator

        if (control.value) {
            control.setValue(control.value);
        }
    }, 0);

As mentioned by @revov this issue occurs when an initial state of form is set and validation is triggered right after form creation. By re-saving controls value in setTimeout callback, validation is run again and form/control status is updated correctly.

.take(1)

I want to say thank you this one is a very good example and helped me see a error in my logic.

NOTE: I figured out a workaround for the debounceTime - just use debounce instead:

export function createAsyncDescriptionValidator(taskService: TaskService): AsyncValidatorFn {

  // NOTE : the take(1) and debounceTime currently wreak havoc on the pending state of the form.
  // This is a great academic exercise but you'd have to remove debounceTime in order to get it
  // to work properly. See https://github.com/angular/angular/issues/13200
  return (control: FormControl): Observable<{ invalidPattern: string} | null> => {
    // on initial load, forms sometimes do not have valueChanges available
    // until the initial edits... assumes that incoming data is valid
    if (!control.valueChanges) {
      return Observable.of(null);
    } else {
      return control.valueChanges
        .debounce(() => Observable.interval(1000))
        .take(1)
        .switchMap(value => taskService.validateDescription(value))
        .map(value => value.valid ? null : {invalidPattern: 'Your task cannot contain the word: poison!'});
    }
  };
}

(from one of my class exercises)

Using debounce over debounceTime seems to work fine. It doesn't unnecessarily make the form pending and it clears as soon as it is done. Guarded my button with:

  <button class="btn btn-primary" 
       [disabled]="taskForm.invalid || (!taskForm.pristine && taskForm.pending)">Save</button>

I want to say thank you this one is a very good example and helped me see a error in my logic.

Updating your validator to Angular 6 and RxJs 6

export function createAsyncDescriptionValidator(taskService: TaskService): AsyncValidatorFn {

  return (control: FormControl): Observable<{ invalidPattern: string} | null> => {

    if (!control.valueChanges) {
      return Observable.of(null);
    } else {
      return control.valueChanges.pipe(
        debounceTime(1000),
        take(1),
        switchMap(value => taskService.validateDescription(value)),
        map(value => value.valid ? null : {invalidPattern: 'Your task cannot contain the word: poison!'})
       )
    }
  };
}

based on @krimple sample code, I implemented mine like so:

private _validateNameNotTaken(control: AbstractControl) {
  if (!control.valueChanges || control.pristine) {
    return Observable.of( null );
  }
  else {
    return control.valueChanges
      .debounceTime( 300 )
      .distinctUntilChanged()
      .take( 1 )
      .switchMap( value => this.unitService.isNameTaken(value) )
      .do( () => control.markAsTouched() )
      .map( isTaken => isTaken ? { nameTaken: true } : null );
  }
}

mine has a few extras, like distinctUntilChanged(), and a hack at the end .markAsTouched() because I wanted to show the error message as it changes directly from the first time, and not only when the focus is out of the input.

but anyways, using debounce instead of debounceTime didn't work for me, so I added || control.pristine in the first If statement. Since I'll be assuming that the data that was previously saved is valid, then there's no need to check when it's just freshly loaded.

This won't work if you're loading invalid data from somewhere else.

I followed this approach and works perfectly, but I got a problem when I change something else, my input move back do invalid. Do you guys have any idea how could I fix this?

I am seeing this exact problem also. My form never leaves pending.
Angular 6.1.10 && rxjs 6.3.3
Here is my async validator

  private validateRestrictions(control: AbstractControl): Observable<ValidationErrors> {
    const statusID = control.get('status.ID').value;
    if (statusID !== 4) {
      // Only validate restrictions if the status is restricted
      return of(null).pipe(delay(250), take(1));
    }
    return control.valueChanges.pipe(
      debounceTime(300),
      distinctUntilChanged(),
      take(1),
      tap(impl => console.log('validating restrictions async...', impl)),
      switchMap(impl => this.implementationService.checkRestrictionsAreUnique(impl)),
      map(isUnique => (isUnique) ? of(null) : of({ restrictionCombinationExists: true })),
      catchError(error => {
        console.error(error);
        return of(null);
      }),
    );
  }

Also, I extracted my observable into a local variable, and subscribed to it, to see what it emits. And know this observable completes. So I think there must be a bug in the way forms handle their async validators

    const obs$ = control.valueChanges.pipe(
      debounceTime(300),
      distinctUntilChanged(),
      tap(impl => console.log('validating restrictions async...', impl)),
      switchMap(impl => this.implementationService.checkRestrictionsAreUnique(impl)),
      map(isUnique => (isUnique) ? null: ({ restrictionCombinationExists: true })),
      catchError(error => {
        console.error(error);
        return of(null);
      }),
      take(1),
    );

    obs$.subscribe(
      (x) => console.log('emitted: ', x),
      (error) => console.error('ERROR: ', error),
      () => console.log('EFFING COMPLETED')
    );
commented

Same issue, using Angular 8

@Hypenate really, I wonder why?
With all the amazing stuff they are doing, so much good stuff, so fast. I wonder if there is some back story on this that makes it always need to be pushed.

Does anyone here know an Angular dev and can ask? Not throwing shade, I am super grateful for Angular tech, just truly curious on this issue.

This issue has plagued me for going on two years now. I've tried 1k combinations of every suggestion on here, but will always end up with one pesky edge case failure.

commented

@j-walker23 I have no clue.
I tried opening the plunker example but it doesn't load for me.
So I've created this Stackblitz (linked to my github).

I was able to reproduce the issue on 3 occasions

  1. The HTTP call fails - In AppModule uncomment the specified line
  2. An endless observable - in IsNumbeZerorAsyncValidator uncomment the specified line
  3. Set the status in a subscription on the valueChanges of the form - open the example on StackBlitz

Many times debounce or debounceTime is used to resolve the issue, this only works if the delay is longer then the time for the async call the return a value.

commented

@jschank See my post above, you are subscribing to the valuechange, but must also subscribe to the statuschange (that can occure later in case of an long operation).

My remote validation service return http code 200 when is valid, 401 if not valid.

this approch work for me when a added finalize and run updateValueAndValidity in root (FormGroup).

return timer(400).pipe(distinctUntilChanged(), 
       switchMap(() => { return http.get(...);  }),
        map(res => null),
        catchError((err) => {
              const errs = err.error;
              const errors = {};
              if (! Array.isArray(errs.errors)) {
                return of({'unique': false});
              }
              (<string[]>errs.errors).forEach(er => {
                Object.assign(errors, {[er]: false});
              });
              return of(errors);

            }),
        ).pipe(take(1), finalize(() => {control.root.updateValueAndValidity({onlySelf: true}); }));

good luck.

@vincent-vieira, I see you closed the issue however multiple people are still reporting this problem. Has it been solved without need for a workaround?

@MatthiasKunnen I should have closed it much sooner, like... two years ago ? Sorry about that.

The 3rd comment from the beginning states it very clearly, this is not a bug but a code mistake on my side.

If anyone is sure that his code is okay and the bug is still reproducible, feel free to comment here and I'll reopen the issue.

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.