@daubihe/controlled-sub-form
v1.0.3
Published
An abstract component to split the complex Angular Reactive Forms
Readme
An abstract component to split the complex Angular Reactive Forms
Complex Angular reactive forms state management based on Profunctor Optics
Install
npm install --save @daubihe/controlled-sub-formUsage
Implement a SubForm Component
Create a sub-form controlled component by extending an imported abstract class. Define two functions for the profuctor lense:
mapControlValueToSubFormthat maps an imported control value to the sub-formmapSubFormValueToControlthat maps a sub-form value to the control
import { ControlledSubFormTypedComponent } from '@daubihe/controlled-sub-form';
interface Gender {
code: string;
}
@Component({
selector: 'app-gender',
templateUrl: './gender.component.html'
})
export class GenderComponent extends ControlledSubFormTypedComponent<Gender> {
public genderLabels = [
{ value: 'MALE', display: 'male' },
{ value: 'FEMALE', display: 'female' }
];
constructor(formBuilder: FormBuilder) {
super(formBuilder);
}
getSubFormConfig() {
return {
code: ['', Validators.required]
};
}
protected mapControlValueToSubForm(value): Gender {
return {
code: value ? value.code : ''
};
}
protected mapSubFormValueToControl(oldCtrlValue: any, newSubFormValue: Gender): any {
if (!newSubFormValue.code) {
return _cloneDeep(oldCtrlValue);
}
return { code: newSubFormValue.code };
}
}
The control's *.html will look as following:
<div [formGroup]="subForm">
<div *ngFor="let genderLabel of genderLabels; let i = index">
<label>
<input type="radio" [value]="genderLabel.value" formControlName="code">
<span>{{genderLabel.display}}</span>
</label>
</div>
<div *ngIf="subForm.get('code').invalid && (subForm.get('code').dirty || subForm.get('code').touched)" style="color: red;">
<div *ngIf="subForm.get('code').errors['required']">
Gender is required.
</div>
</div>
</div>Please notice that subForm FormGroup will be created automatically by the ControlledSubFormTypedComponent class according to the getSubFormConfig method defined in the component.
Use a SubForm Component
Now you can use this gender component in your forms by assigning to it a control (the only @Input() param) that will contain the data. Data will be then mapped to a subForm inside the gender component and re-mapped back once changed
<!-- app.component.html -->
<form [formGroup]="form">
<app-gender [control]="form.get('gender')"></app-gender>
</form>The value for the control can be passed down in this way:
@Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
form: FormGroup;
constructor(private formBuilder: FormBuilder) {
this.form = this.formBuilder.group({
gender: {code: 'MALE'}
}});
}
}This allows us to split the validation and form creation logic between our controlled sub-form components and still have a reactive value propagation and validation flow (so the parent does not need to know about the data types of children components and the way how to validate them)
