I am still learning Angular 5 and have started working with the "reactive" form model. However, almost every example and tutorial I can find has you creating the entire form in one template. Normally in AngularJS 1.x, we would store each field in its own directive and then wire them together to create a form to cut down on duplication.
Is there a way to do this with Angular 5 reactive forms using only one file and having the template and all validations included? I can see how to do it in two pieces, where I would have a component that contains the form element HTML, validation messages, etc. but then you also need to create the FormControl in the full form's component and give it its default value and validations.
Maybe this is extremely common and I am just not searching for it correctly, but if anyone can point me towards any patterns, tutorials or help I would greatly appreciate it as I feel it is the last piece I am missing for my forms. Thank you!
In case anyone else comes across this, the best answer I could find (and which is at least used by many other devs out there, even if not a best practice) is to create a FormGroup in the parent "main form" component and then create and attach a new FormControl to that FormGroup. For example, here is the component for the re-usable form control:
import {Component, OnInit, Input} from '@angular/core';
import {FormControl, FormGroup, Validators} from '@angular/forms';
@Component({
selector: 'app-project-length',
templateUrl: './project-length.component.html',
styleUrls: ['./project-length.component.css']
})
export class ProjectLengthComponent implements OnInit {
@Input() isFormSubmitted: boolean;
@Input() projectForm: FormGroup;
constructor() {
}
ngOnInit() {
this.projectForm.addControl('projectLength', new FormControl(0, [Validators.required, this.hasCorrectLength]));
}
hasCorrectLength(control: FormControl): {[s: string]: boolean} {
const length: number = control.value;
if (length < 2 || length > 10) {
return { 'incorrectLength' : true };
}
return null;
}
}
Here is the template for that form element component:
<div class="form-group" [formGroup]="projectForm">
<label for="project-length">project length</label>
<input
class="form-control"
type="number"
id="project-length"
placeholder="Enter project length"
formControlName="projectLength"
/>
<span class="help-block"
*ngIf="!projectForm.controls['projectLength'].valid && (projectForm.controls['projectLength'].touched || isFormSubmitted)">
please enter a project length between 2 and 9
</span>
Here is the component of the parent form (which already has some built-in form elements from a tutorial I was working on, and only one re-usable form element component):
import { Component, OnInit } from '@angular/core';
import {FormControl, FormGroup, Validators} from '@angular/forms';
import { Status} from '../shared/Status';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit{
projectForm: FormGroup;
statuses: Array<Status> = [
{id: 1, name: 'Critical'},
{id: 2, name: 'Stable'},
{id: 3, name: 'Finished'}
];
formSubmitted: boolean = false;
ngOnInit() {
this.projectForm = new FormGroup({
namey: new FormControl(null, [Validators.required, this.cannotBeTest1]),
email: new FormControl(null, [Validators.required, Validators.email]),
status: new FormControl('1')
});
}
onSubmit() {
console.log(this.projectForm);
this.formSubmitted = true;
if (this.projectForm.valid) {
console.log('Form data:');
console.log(this.projectForm);
}
}
cannotBeTest1(control: FormControl): {[s: string]: boolean} {
...
}
}
And here are the important parts of the template for the main form component:
<div class="container">
<div class="row">
<div class="col-xs-12 col-sm-10 col-md-8 col-sm-offset-1 col-md-offset-2">
<form class="ui form-vertical" [formGroup]="projectForm" (ngSubmit)="onSubmit()">
...
<app-project-length [projectForm]="projectForm" [isFormSubmitted]="formSubmitted"></app-project-length>
...