Search code examples
angularrxjsobservableangular-reactive-forms

Use of combineLatest with valueChanges in ngOnInIt


I am trying to use the combineLatest with reactive form's valueChanges method in NgOnInIt hook. My use case is below.

I want to show the name which received from Observable1$ in the form using patchValue() from Reactive forms. This name field is editable which means when user wants to edit the name which is already shown in the form, I want to check if the name is not the same and if it is not then I want to enable the button to call PUT method. Hence, I want to use combineLatest from RxJS to handle this behavior but I am not able to do that so. Below errors I am getting.

Actual Behaviours:

  1. I get error saying that Maximum call stack size exceeded because of updateValueAndValidity() as it goes into infinite loop.
  2. I did add {emitEvent: false} in patchValue() but when I tried to edit the field, due to this {emitEvent: false}, nothing is emitting anymore.

Expected behaviour:

Formfield should show the data which I received from observable1$ (ex: testName). When I want to edit this field (ex: testNameE), valueChanges method should emit {description: testNameE} value.

How I am trying to achieve this.

form-component.ts

  ngOnInit() {
    combineLatest([
      this.observable1$.pipe(startWith(null)), - provides name
      this.form.valueChanges.pipe(startWith(null)), - wants to get the input from the user when he tried to change the name
    ]).pipe(
      tap(([name, formData]) => {
        if (name) {
          this.patchValue(name);
        }
        if (formData) {
        // check the name is different than received from observable1$,
        // if yes, enable button
        }
      }),
    ).subscribe();
  }
  
  patchValue(name: string) {
    this.form.patchValue({
      name: name,
    });
    this.form.updateValueAndValidity();
  }

Can someone please tell me what I am doing wrong here. Any kind of help is appreciated.

Below is the link for stackblitz. https://stackblitz.com/edit/angular-ivy-zjpwxb?file=src/app/app.component.ts


Solution

  • I think the simplest solution here is to separate out the patch form logic to only depend on observable1$ and not value changes

    My personal preference is to not use tap unless debugging or explicitly doing mid stream side effects i.e. I'm using async pipe so can't subscribe.

    
      ngOnInit() {
        // patch name to form
        this.observable1$.pipe(
          filter(v => !!v),
          takeUntil(this.destroyed$)
        ).subscribe(name => this.patchValue(name))
    
        // for use with a async pipe
        this.showSaveButton$ = combineLatest([this.observable1$, this.form.valueChanges])
          .pipe(
            filter(([name, _]) => !!name)
            map(([name, formData]) => {
              // check different
              return name !== formData.name
            }),
            takeUntil(this.destroyed$),
            startWith(false)
          )
      }
      
      patchValue(name: string) {
        this.form.patchValue({
          name: name,
        });
        this.form.updateValueAndValidity();
      }