1

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?

2
  • If you need a custom component to act as a normal formControl, you’ll need to go the controlValueAccesor route. Which really is not very complex.. Commented Jan 6, 2021 at 15:05
  • You have to pass formControl to child component and have to use standalone formControl directive to make it work. some think like this [formControl]="appFormControlName" Commented Jan 6, 2021 at 15:19

3 Answers 3

1

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".

Sign up to request clarification or add additional context in comments.

1 Comment

I'll head down this road and give it a try. Thanks.
1

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

Comments

0

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 ids 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 checkboxes, texareas, and other controls, and having separate ControlValueAccessors 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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.