I have a nested reactive form which I want to split into several components.
advanced form (parent)
advanced.component.ts
I define the basic form, but I would like to avoid to define the form models for the subcomponents in the parent, because I think they don't belong here.
buildChamberForm() {
return this.formBuilder.group({
_id: [''],
__v: [''],
name: [ '', Validators.required ],
cycle: [ '', Validators.required ],
strains: this.formBuilder.array([])
rules: this.formBuilder.array([])
})
}
buildChambersForm() {
return this.formBuilder.group({
chambers: this.formBuilder.array( [] )
})
}
advanced.component.html
<app-setting-strains [chamberStrains]="chamber.strains" [chamberCycle]="chamber.cycle"></app-setting-strains>
<app-setting-lights [chamberRules]="chamber.rules" [chamberCycle]="chamber.cycle"></app-setting-lights>
<app-setting-fans [chamberRules]="chamber.rules"></app-setting-fans>
setting-fans (1st child)
The chamber rules splits into different child components, e.g. rules for fans.
<app-input-rule [chamberRules]="chamberRules" [ruleDevice]="'fan'" placeholder="'e.g. Air circulation (Left)'"></app-input-rule>
input-rule form (2nd child)
In (some of) those child components the input-rule form is used. Other child omponents use other input components.
input-rule.component.ts
createRuleForm(device?, trigger?) {
const ruleForm = this.formBuilder.group({
_id: [''],
sensor: [''],
device: [device || ''],
forDetector: [''],
detectorId: [''],
trigger: [trigger || ''],
startTime: [''],
durationHOn: [''],
durationOn: [''], // TODO: this is temporary
timeUnit: [''], // TODO: this is temporary
durationMSOn: [''],
onValue: [''],
offValue: [''],
onPattern: [''],
durationMBlocked: [''],
nightOff: [''],
relay: ['']
})
return ruleForm
}
createRulesForms() {
const rulesForm = this.formBuilder.group({
rules: this.formBuilder.array( [] ) // TODO: validator depending on rule type
})
// console.log("rulesForm", rulesForm)
return rulesForm
}
input-rule.component.html
This is an example of the use of a component in the input-rule component. There are several inputs in the input rule component.
<div class="row">
<div class="col-xs-12 col-sm-12 col-md-8">
<app-input-relay *ngIf="rule.get('relay')" [label]="capitalizeFirstletter(ruleDevice) + 'Relay'" [class]="'tutorial-step'" [relay]="rule.get('relay')" [placeholder]="placeholder"></app-input-relay>
</div>
</div>
input relay (3rd child), input-relay.component.html
Input relay is one of several components to set params of the rule.
<div class="form-group col-xs-6 col-sm-6" *ngIf="relay.value">
<label>Rename Relay</label>
<input type="text" class="form-control" [ngClass]="class" [formControl]="name" [placeholder]="placeholder" (keyup)="debouncedRename(this.relay.value, this.name.value)"/>
</div>
Goal
to keep the subforms in the child components, instead of defining the whole form in the parent component. Or if this is the right approach to move everything into a form builder service.
- use the submit method on the parent form to start the validation of a ll subforms => passing up the data from the child forms
Questions
In my current setup I cannot use to setValue to initialize the form in the advanced component, beacause the advanced component has no knowledge about the subforms in e.g. the FormArray strains thus it errs:
There are no form controls registered with this array yet.
I can solve this problem by using patchValue but I think this in anti pattern.
Is it better to define the form in the parent.component or should I move everything to a service which has all the form building methods?
1.1 If I leave it in the parent, how to do this?
1.2 If I move it to a service, should the service be stateful and contain the form values or is better to just geenrate the form in the service and pass it down like in the html example above with inputs?
1.2.1 Will I need outputs for this as well, I read somewhere that the reactive form handles this somehow.
Those a serval questions but they are all basically one question, what is the right way to split a complex nested form into several components.
EDIT:
This in an example with a service:
https://blog.grossman.io/real-world-angular-reactive-forms/
But most examples put the form logic into the component (but they also do not show how to split between components):
- https://angular.io/guide/reactive-forms#generating-form-controls-with-formbuilder
- https://angularfirebase.com/lessons/basics-reactive-forms-in-angular/
- https://malcoded.com/posts/angular-fundamentals-reactive-forms
This one uses Inputs/Outputs:
(but I read somewhere this shouldn't be done with reactive forms)