Reputation: 881
I am using Angular signals with Reactive Forms and facing an issue where the hasFormControlError
and validations
computed signals are not updating the view when the formControl
input signal value changes. I am passing reactive form control instances in the formControl
signal input,
The issue here is computed signals reflecting updates in the view only when submitted, but if the control is dirty, invalid at the time of key press, even the errors object nothing is reflecting in the view by the computed signals.
However, if I change them to getters, they work fine and reflecting everything. I am unable to understand why this is happening what i am doing wrong here.
Stackblitz - https://stackblitz.com/edit/stackblitz-starters-ur795u?file=src%2Fmain.ts
This is how i am passing the form control instance from parent
<form-control [form-control]="formControls()['email']"></form-control>
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { fadeAnimation } from '@shared/animations';
import { generateNumericId } from '@shared/utilities';
import { AbstractControl, FormControl } from '@angular/forms';
import { FormControlValidationService } from '../../services';
import { IControlConfig, IFormControlValidation } from '../../models';
import { Component, computed, inject, input, output } from '@angular/core';
@Component({
standalone: true,
selector: 'form-control',
animations: [fadeAnimation],
imports: [CommonModule, FormsModule],
styleUrl: './form-control.component.css',
providers: [FormControlValidationService],
templateUrl: './form-control.component.html'
})
export class FormControlComponent {
private _validationService = inject(FormControlValidationService);
public formControl = input.required<AbstractControl | FormControl>({
alias: 'form-control',
});
public controlConfig = input({}, {
alias: 'control-config',
transform: (value: IControlConfig) => {
return {
isRequired: true,
showErrorMessages: true,
...value
};
}
});
public hasFormControlError = computed<boolean>(() => {
return !!(
(this.formControl().invalid && this.formControl().dirty) ||
(this.controlConfig().submitted && this.formControl().errors)
);
});
public validations = computed<IFormControlValidation[]>(() => {
return Object.keys(this.formControl().errors || {}).map((key) => {
const validationError = {
errorType: key,
errorMessage: '',
id: generateNumericId(),
formControl: this.formControl(),
error: this.formControl().errors?.[key]
};
validationError['errorMessage'] =
this._validationService.getControlErrorMessage(validationError);
return validationError;
});
});
}
Upvotes: 3
Views: 2124
Reputation: 1248
If you expect the computed signal to be recalculated, than of course it does not. The reactive nature of signals is based on the signals ability to notice that its value changed. But the form control does not change. It is still the same reference. Yes, the values inside its properties change, but the signal does not know it, so it does not "signal".
When you use signals, you need to store immutable data in them. If you use normal objects, that mutate, then the signal will not be aware of it, and computed
signals will not be recalculated. (also, effects will not re-execute).
What you could do, is receive the form control as normal input, and then convert their statusChanges
observable into a signal using the toSignal
method.
As a side note - reactive forms is probably the only core angular feature that is not based on the same paradigm as signals. They currently do not work so well together. I believe the angular team will provide an alternative approach to forms that is based on immutable data.
Upvotes: 0