Reputation: 8918
I have a reactive form inside a component, like this:
<form [formGroup]="edit">
<div class="form-group row">
<label for="{{componentId}}_iptItemName" class="col-4 col-form-label">Name</label>
<div class="col-8">
<input id="{{componentId}}_iptItemName" type="text" class="form-control"
formControlName="name"/>
</div>
</div>
<!-- more form groups -->
</form>
Most of the fields look the same, so I would like to take the div.form-group
and make a reusable component. The problem is, I can't get the formControlName
into the nested component.
When I try just specifying formControlName
on the new component, I get:
No value accessor for form control with name: 'name'
When I use an @Input() appFormControlName: string
and pass that value to the input
's [formControlName]
, I get:
formControlName must be used with a parent formGroup directive. You'll want to add a formGroup directive and pass it an existing FormGroup instance (you can create one in your class).
I've seen things where I could create an extra form group for each nested component, or register some kind of custom ControlValueAccessor
to make it a "true" form control, but both of those seem overly complicated for what I'm doing. I just want to create a component to keep things DRY, not add complexity by creating custom form controls or kludge in single-value form groups.
Am I just missing something simple?
Upvotes: 1
Views: 768
Reputation: 8918
I ended up using <ng-content>
and just keeping the input in the form component.
<app-card-input label="Name">
<input type="text" class="form-control" formControlName="name"/>
</app-card-input>
I also set up the component to create the id
s on the fly:
let next = 0;
@Component({
selector: 'app-card-input',
templateUrl: './card-input.component.html',
styles: []
})
export class CardInputComponent implements OnInit, AfterViewInit {
@Input() label: string;
componentId = `app-card-input-id-${next++}`;
constructor(private elr: ElementRef) {
}
ngOnInit(): void {
}
ngAfterViewInit(): void {
const ipt = this.elr.nativeElement.querySelector('input, select, textarea');
ipt.setAttribute('id', this.componentId);
}
}
I mostly decided to go this route because it gives me the flexibility to use different control types. I will need checkbox
es, texarea
s, and other controls, and having separate ControlValueAccessor
s for each (or trying to cram them all under one) could get cumbersome.
Thanks @Petr and @Bojan for your answers. They definitely pointed me in the right direction.
Upvotes: 0
Reputation: 9486
Short answer just name component input differently e.g. @Input() myFormControlName
Long answer There is a difference between (as I call them) "custom component" and "custom control". When writing custom component avoid "standard" names for Input, Output such as ngModel, formControl, formControlName, etc. And usage looks like:
<my-component [myData]="data" (myDataChange)="change()">
or you may pass formControl/formGroup or whatever:
<my-component [myFormGroup]="someFormGroup" "myControlName"="someControlName">
// 'myControlName' IS @Input of component
As for "custom control" I'll give you https://github.com/angular/components/blob/master/src/material/checkbox/checkbox.ts as an example. And usage is:
<mat-checkbox [(ngModel)]="checked">Checked</mat-checkbox>
or
<mat-checkbox formControlName="checkBoxName">Checked</mat-checkbox>
// 'formControlName' IS NOT @Input of component
Upvotes: 1
Reputation: 5649
ControlValueAccessor seems complicated at first, but exactly what you need. It will solve many problems later. It's quite easy to use it, just a lot of "boilerplate".
Upvotes: 1