3

I have form group in my angular app. Since I would like to aviod writing onChange for each form element, my idea was to subscribe to entire form changes, and then on event check changed element

So this is relevant part of the code:

constructor(){
this.orderForm = this.formBuilder.group({
  ...
});

this.event$ = this.orderForm.valueChanges.subscribe((x:any) => {
  console.log('form value changed')
  console.log(x)
});

Problem is, this x is just entire form so I have no idea what element is changed

Is there any smart way to find out what form element changed?

3 Answers 3

2

I don't thing there is a function that returns only the formControls that have changed.

But since valueChanges is an observeable you could use pairwise to get the previous and the next value. And compare them to find out what has changed.

this.orderForm.valueChanges
  .pipe(startWith(null), pairwise())
  .subscribe(([prev, next]: [any, any]) => ... );
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks, this works but it's kind of not what I'm trying to do. Looping through 50+ elements on multiple levels on every change (and there are multiple text inputs, that means it's whole loop for each letter), comparing them to other form, just seems like overdoing things. Guess my best option is then single change function that gets formelement name and ngModelChange in every form field
2

This works well if you have a simple form with just formcontrols, no nested groups or FormArrays.

You can use rxjs merge for each form controls value changes and figure out which one changed:

merge(
  ...Object.keys(this.orderForm.controls).map(
    (controlName: string) =>
      this.orderForm.get(controlName).valueChanges.pipe(
        tap((value) => {
          console.log(`Control "${controlName}" changed. New value: ${value}`)
        })
      )
  )
).subscribe();

Here's a STACKBLITZ for your reference.

1 Comment

Thanks but my form has nested groups, otherwise nice solution
1

"stolen" the idea of AT82, we can has a recursive function that return an observable. Well, instead of use "tap", we can use map to return an object with two properties: name (the control has changed) and value.

So some like

  mergeForm(form: FormGroup | FormArray, suffix: string) {
    if (form instanceof FormArray) {
      console.log(suffix)
      const formArray = form as FormArray;
      const arr = [];
      for (let i = 0; i < formArray.controls.length; i++) {
        const control = formArray.at(i);
        arr.push(
          control instanceof FormGroup
            ? this.mergeForm(control, suffix + i+'.')
            : control.valueChanges.pipe(
                map((value) => ({ name: suffix + i, value: value }))
              )
        );
      }
      return merge(...arr);
    }
    return merge(
      ...Object.keys(form.controls).map((controlName: string) => {
        const control = form.get(controlName);
        return control instanceof FormGroup || control instanceof FormArray
          ? this.mergeForm(control, suffix + controlName + '.')
          
          : control.valueChanges.pipe(
              map((value) => ({ name: suffix + controlName, value: value }))
            );
      })
    );
  }

We can call in the way

this.mergeForm(this.orderForm, '')
  .pipe(takeWhile((_) => this.alive))
  .subscribe((res) => {
    console.log(res);
  });

You can see the stackblitz

NOTE: If you don't use FormArray you can simply the recursive function

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.