My application has many forms and inputs, so I created various custom form components (for normal text inputs, textarea inputs, selects, checkboxes, etc.) so that I don't need to repeat verbose HTML/CSS and form validation UI logic all over the place.
My custom base form component looks up its hosting FormGroupDirective and uses its submitted property in addition to its FormControl states (valid, touched, etc.) to decide which validation status and message (if any) needs to be shown on the UI.
This solution
- does not require traversing through the form's controls and modifying their status
- does not require adding some additional
submitted property to each control
- does not require any additional form validation handling in the
ngSubmit-binded onSubmit methods
- does not combine template-driven forms with reactive forms
form-base.component:
import {Host, Input, OnInit, SkipSelf} from '@angular/core';
import {FormControl, FormGroupDirective} from '@angular/forms';
export abstract class FormBaseComponent implements OnInit {
@Input() id: string;
@Input() label: string;
formControl: FormControl;
constructor(@Host() @SkipSelf()
private formControlHost: FormGroupDirective) {
}
ngOnInit() {
const form = this.formControlHost.form;
this.formControl = <FormControl>form.controls[this.id];
if (!this.formControl) {
throw new Error('FormControl \'' + this.id + '\' needs to be defined');
}
}
get errorMessage(): string {
// TODO return error message based on 'this.formControl.errors'
return null;
}
get showInputValid(): boolean {
return this.formControl.valid && (this.formControl.touched || this.formControlHost.submitted);
}
get showInputInvalid(): boolean {
return this.formControl.invalid && (this.formControl.touched || this.formControlHost.submitted);
}
}
form-text.component:
import {Component} from '@angular/core';
import {FormBaseComponent} from '../form-base.component';
@Component({
selector: 'yourappprefix-form-text',
templateUrl: './form-text.component.html'
})
export class FormTextComponent extends FormBaseComponent {
}
form-text.component.html:
<label class="x_label" for="{{id}}">{{label}}</label>
<div class="x_input-container"
[class.x_input--valid]="showInputValid"
[class.x_input--invalid]="showInputInvalid">
<input class="x_input" id="{{id}}" type="text" [formControl]="formControl">
<span class="x_input--error-message" *ngIf="errorMessage">{{errorMessage}}</span>
</div>
Usage:
<form [formGroup]="form" novalidate>
<yourappprefix-form-text id="someField" label="Some Field"></yourappprefix-form-text>
</form>
formGroup.validorformControl.valid..ng-dirty.ng-invalidis only suitable to certain flow (like their tutorial). You have to modify it to something else. For example, you can define error styling for this selector.ng-dirty.ng-invalid, .submit-attempt.ng-invalid, then in yourinput, add[class.submit-attempt]="submitAttempt", and declare the boolean variablesubmitAttemptin your component, turn it to true in yoursubmitfunction.