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:
- I get error saying that Maximum call stack size exceeded because of updateValueAndValidity() as it goes into infinite loop.
- 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>