FormGroup in angular does not correctly see single changes of its children if they listen to each other through valueChanges

Viewed 111

I have got simple situation. There is a html form with two fields:

  • firstName
  • lastName

I want lastName to change for something when user changes firstName. I implemented it through valueChanges observable in Angular (8).

Besides I have two subscription to listen and log changes:

  • one on whole form
  • one on lastName

Here is how it is implemented:

 ngOnInit(){
    this.form = new FormGroup({
      firstName: new FormControl('', {updateOn: 'blur'}),
      lastName: new FormControl('', {updateOn: 'blur'}),
    });
    this.form.get('lastName').valueChanges.subscribe(value => console.log('lastName', value));
    this.form.valueChanges.subscribe(value => console.log('Value', value));

    this.form.get('firstName').valueChanges
      .subscribe(firstName => {
        this.form.get('lastName').setValue(`firstName ${firstName} ${new Date().getMilliseconds()}`);
      });
  }

In general - from a user point of view - this behaves as expected. User changes firstName and then lastName changes itself.

But when I look at the console log, here is what I've got:

lastName firstName test 850 
Value > {firstName: "test", lastName: "firstName test 850"}
Value > {firstName: "test", lastName: "firstName test 850"}

I expected two logs from whole, but one with first name changed, and second with lastName changed. Something more like this:

Value > {firstName: "test", lastName: ""}
lastName firstName test 850 
Value > {firstName: "test", lastName: "firstName test 850"}

Here is a whole example on stackblitz.

Ok, so when I changed a way I update lastName from this:

this.form.get('lastName').setValue(`firstName ${firstName} ${new Date().getMilliseconds()}`);

to this:

setTimeout(() => this.form.get('lastName').setValue(`firstName ${firstName} ${new Date().getMilliseconds()}`));

then logs are in order which I expected:

Value > {firstName: "test", lastName: ""}
lastName firstName test 850 
Value > {firstName: "test", lastName: "firstName test 850"}

However, setTimeout seems to be tricky hack here. Here is whole code on stackblitz.

The question is: What is going on here? Why FormGroups works in that way? Is there a pattern to process such events in Angular (when one form affects another)?

1 Answers

This is how event loop works

What's happening in both cases :

Without setTimeout

  1. User set firstName
  2. firstName.valueChanges is added to EventLoop queue
  3. EventLoop runs and call operation firstName.valueChanges
  4. lastName.setValue is called
  5. lastName.setValue add lastName.valueChanges and form.valueChanges to eventLoop queue
  6. lastName.valueChanges add form.valueChanges to eventLoop queue
  7. lastName.valueChanges is dequeued
  8. form.valueChanges (from lastName.valueChanges) is dequeued
  9. form.valueChanges (from firstName.valueChanges) is dequeued

With setTimeout

  1. User set firstName
  2. firstName.valueChanges is added to EventLoop queue
  3. EventLoop runs and call operation firstName.valueChanges
  4. setTimeout(lastName.setValue) is not called but added to the event loop queue
  5. firstName.valueChanges adds form.valueChanges to event loop queue
  6. setTimeout is dequeued, he sets lastName value and adds lastName.valueChanges to tevent loop queue
  7. form.valueChanges (from firstName.valueChanges) is dequeued
  8. lastName.valueChanges is dequeued and adds form.valueChanges to event loop queue
  9. form.valueChanges (from lastName.valueChanges) is dequeued
Related