Form manipulation is a typical task for a Front-End engineer. They are used everywhere - from simple feedback forms to a complex ones for creating something big. While you are using any of the UI frameworks or libraries, they offer us a convenient way to create forms of any complexity easily and quickly.
With Angular, we have two options for form building: template-driven and reactive forms. Both of them can be applied in different scenarios. For example, if we need to create some simple form with primitive (or even without) validation, we can consider a template-driven form as a good candidate. Another case is when we need to build something more complex than a login form, with tons of validation, field dependencies from another field, etc. In this way, reactive forms come on the stage. But even with this powerful tool, we sometimes need to build some functionality that is not available from the box. In this article, I will show one of the possible cases, and you can apply this approach to your project.
Let's cut to the chase and move to the problem: we need to update a form field based on a value from another form field. Moreover, to enhance the application experience and not confuse a user, we want to highlight (put some message and color the field) the updated field. The fields can be any kind of element (input, dropdown, date picker, etc.), and any combination of field values can lead to the updation of another field. How can we solve this problem? There are some possible ways:
First things first, we need to create a class for our new control, which extends the FormControl class:
import { FormControl } from '@angular/forms';
export class AutoUpdateFormControl<TValue> extends FormControl {
constructor() {
super();
}
}
Then, to add a new functionality, we need to write our method for the control updating and mark the inner state as auto-updated:
import { FormControl } from '@angular/forms';
export class AutoUpdateFormControl<TValue> extends FormControl {
public isAutoUpdated: boolean = false;
constructor() {
super();
}
public autoUpdateValue(value: any, options?: { emitEvent: boolean }): void {
super.setValue(value, options);
this.markAsAutoUpdated();
}
public setValue(
value: TValue,
options?: {
onlySelf?: boolean;
emitEvent?: boolean;
emitModelToViewChange?: boolean;
emitViewToModelChange?: boolean;
},
): void {
super.setValue(value, options);
this.markAsNotAutoUpdated();
}
public markAsAutoUpdated(): void {
this.isAutoUpdated = true;
}
public markAsNotAutoUpdated(): void {
this.isAutoUpdated = false;
}
}
An example of usage:
interface IForm {
sourceField: AbstractControl<string>;
targetField: AutoUpdateFormControl<string>;
}
const form = new FormGroup<IForm>({
sourceField: new FormControl(''),
targetField: new AutoUpdateFormControl(),
});
sourceField
control and check the required value. If matches - call the autoUpdateValue method. It sets the control value and updates the control field:form.controls.sourceField.valueChanges.subscribe((value) => {
if (value === 'test') {
this.form.controls.testField.autoUpdateValue('updated field');
}
});
<span *ngIf="form.controls.targetField.isAutoUpdated">Auto-updated</span>
If you use some custom components or the wrappers around the third-party ones, you can directly add this kind of template into the component to not repeat it again and again.
I found this solution perfect for my needs and decided to share it. Perhaps, it will help someone. But if you want to go further, remember:
With these bits of advice, you can create your custom controls safely and with pleasure.