Home > Software engineering >  Angular formControl: subscribe after form update
Angular formControl: subscribe after form update

Time:12-21

Consider this dummy scenario where I have exactly one form with exactly one simple control on it which has a required validator (i.e. there must be some text in it).

When I subscribe to the control's valueChanges, it always get for that field the new value. This is desired behaviour.

But the problem now is when I enter some text in an input field bound to that form control that within my subscription

control.valueChanges.subscribe((value) => {
  console.log('form valid: ', this.form.valid);
  console.log('control valid: ', control.valid);
});

The output will be

form valid: false

control valid: true

Which is inconsistent since there is only one control present. This is I assume because this subscription is executed before the form itself could subscribe and also update its validity and value (form.value['myField'] is still null).

How could I still achieve this behaviour with a single subscription to the control? I really would like NOT to subscribe to the formGroup itself nor make multiple subscriptions.

EDIT Is there maybe some RXJS operator or so to simply let the subscription callback run last? Such that the form group has already done its work when I pick in on it.

CodePudding user response:

It seems like the problem you are running into is that the form is only updated on the next change detection cycle. You need to schedule your code to run on the next change detection cycle a common pattern to do this is to just use setTimeout. So your function would look like this:

control.valueChanges.subscribe((value) => {
  setTimeout(() => {
    console.log('form valid: ', this.form.valid);
    console.log('control valid: ', control.valid);
  });
});

CodePudding user response:

You can do it with observeOn pipe operator:

control.valueChanges
.pipe(
  observeOn(asapScheduler),
)
.subscribe((value) => {
  console.log('form valid: ', this.form.valid);
  console.log('control valid: ', control.valid);
});

CodePudding user response:

It might be helpful to understand what problem you are trying to solve by subscribing to the single controls value changes. It seems like you are trying to execute some logic when the value is changed and the form is valid - but that's me speculating.

I know you said you'd rather not make multiple subscriptions and would rather not subscribe to the form group, but that seems like an arbitrary restriction to me. RxJS gives us lots of tools to compose observables so we can emit when we need to.

Here's one way you can have a single subscription to know when the control has been changed to a valid state and the form group itself is valid. In this snippet, I combine changes made to the specific control, as well as changes to the form status. Then I filter based on whether the control and form group are actually considered valid. Lastly, in my subcription callback I can execute whatever logic is needed.

const control = this.myForm.controls['myField'];
combineLatest([control.valueChanges, this.myForm.statusChanges])
  .pipe(
    filter((changes) => {
      const ctrlChanges = changes[0];
      const formStatusChanges = changes[1];

      console.log(`New Value: ${ctrlChanges}`);
      console.log(`Form Status: ${formStatusChanges}`);

      return control.valid && formStatusChanges == 'VALID';
    })
  )
  .subscribe(() => {
    console.log('Form is valid!');
  });

}

Here's a strackblitz demonstrating this.

CodePudding user response:

I'm sorry to tell you that the Reactive Forms in Angular does not design to work that way. So there's no solution, just workaround. I personally don't have an use cases for this so won't be able to give you any workaround. Just here to explain why it works the way it is.

Internally, when there's an update, it will run updateValueAndValidity for the control and from the control, run that function for its direct parent.

  • Recalculates the value and validation status of the control.
  • By default, it also updates the value and validity of its ancestors.
updateValueAndValidity(opts: { onlySelf?: boolean; emitEvent?: boolean } = {}): void {
    //valueChanges already fired => console.log already printed for control
    (this.valueChanges as EventEmitter<TValue>).emit(this.value);
    (this.statusChanges as EventEmitter<FormControlStatus>).emit(this.status);

    //So much later, the parent get invoked
    this._parent.updateValueAndValidity(opts);
  }

You can see the source code here: https://github.com/angular/angular/blob/22871e952d79f5a25d757e777684b524abf967e4/packages/forms/src/model/abstract_model.ts#L1030

CodePudding user response:

I suggest you subscribtion of form values changes:

this.form.valueChanges.subscribe(x => {
  const control = this.form.get('myField');
  console.log(x, this.form.valid, control.valid);
});
  • Related