Home > Mobile >  Use of combineLatest with valueChanges in ngOnInIt
Use of combineLatest with valueChanges in ngOnInIt

Time:11-18

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

CodePudding user response:

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();
  }

CodePudding user response:

To check whether a form is valid based on field values you need to use Form Validators.

In your case if you want to check if a field value is not the same as a value gotten from a service you can create a custom form validator.

You can then disable the submit button if the form isn't valid.

component.ts

import { Component, OnInit } from '@angular/core';
import { AbstractControl, FormBuilder, FormGroup, ValidationErrors, Validators } from '@angular/forms';
import { delay, Observable, of } from 'rxjs';

@Component({
  selector: 'app-temp',
  templateUrl: './temp.component.html',
  styleUrls: ['./temp.component.scss']
})
export class TempComponent implements OnInit {

  nameFromService?: String;
  nameForm?: FormGroup;
  
  constructor(private formBuilder: FormBuilder) { }

  ngOnInit(): void {
    this.nameForm = this.formBuilder.group({
      name: ['', [Validators.required, this.checkNameIsServiceName] ]
    });

    this.setNameFromService();
  }

  checkNameIsServiceName = (control: AbstractControl): ValidationErrors | null => {
    if(this.nameFromService == control.value) return {forbiddenName: {value: control.value}};
    else return null;
  }

  setNameFromService(){
    // TODO GET name from service that returns an observale
    let obsStringName: Observable<String> = of('TEST').pipe(delay(1000));

    obsStringName.subscribe(result => {
      // set variable value and form field value
      this.nameFromService = result;
      this.nameForm?.patchValue({name: result});
    });
  }

  onSubmit(){
    // PUT request
  }
}

component.html

<div *ngIf="nameForm && nameFromService">
    <form [formGroup]="nameForm">
        <input type="text" placeholder="Enter username" formControlName="name" />

        <button type="submit" 
            (click)="onSubmit()"
            [disabled]="!nameForm.valid">
            Put User
        </button>
    </form>
</div>
  • Related