How to wrap custom Angular Material control into own component?

1.2k Views Asked by At

I'm trying to create a custom control based on MatFormField. To start with, I went through the Angular Material documentation in the section on how to create your own custom control. An example from Angular Material I'm trying over:

https://material.angular.io/guide/creating-a-custom-form-field-control

From the documentation, you can jump to an example directly on Stackblitz.

I mean creating a completely new template, e.g. input, which will keep the styles from the Material, not wrapping the usual Material control - exact example in the link above.

According to the documentation, everything works fine. However, I am annoyed by the fact that every time I want to use such a control, I have to redefine <mat-form-field> etc. I tried in various ways to implement a wrapper for such a control, but to no avail.

I would like to create an identical control as in the example above, but with an additional wrapper of the "form-field-custom-control-example" component - I mean some wrapper for <mat-form-field> as a new component, which I can then call in the form template.
For example what I would like to achieve:

<form [formGroup]="form">
  <form-field-custom-control-example [formControlName]="someControl"></form-field-custom-control-example>
</form>

I'm not even sure if I should also implement ControlValueAccessor on this wrapper and perhaps use references to refer to the methods of ControlValueAccessor which is defined in the nested control? Or maybe with @Input decorator itself pass formControl/formControlName?

I tried both approaches but maybe I made some mistakes along the way or approached the topic wrong.

Is there any dedicated solution for such a scenario or does anyone know a nice method to implement such a wrapper? I will be grateful for any tips and examples.

1

There are 1 best solutions below

1
Eliseo On BEST ANSWER

Generally you can use viewProvider in the way, e.g.

@Component({
  selector: 'app-input',
  template: `<h1>Hello </h1>
  <input [formControlName]="name"/>
  `,
  styles: [`h1 { font-family: Lato; }`],
  viewProviders:[{ provide: ControlContainer, useExisting: FormGroupDirective }]
})
export class HelloComponent  {
  @Input() name: string;
  
  constructor(){}
}

And use the component like

  <app-input name="control" > </app-input>

  form=new FormGroup({
    control:new FormControl()
  })

But (And I don't know why) using mat-input not work :(. So we can use a work-around: inject in constructor the formGroupDirective and use ngAfterViewInit to reference the control to the FormControl of the formGroup

The component like

<form class="example-form">
  <mat-form-field class="example-full-width">
    <mat-label>Favorite food</mat-label>
    <input matInput [formControl]="control" placeholder="Ex. Pizza" >
  </mat-form-field>
</form>

  control:FormControl
  @Input() controlName=''
  constructor(@Host() private parentF: FormGroupDirective) { }
  ngAfterViewInit()
  {
    this.control=this.parentF.form.get(this.controlName) as FormControl 
                                               || new FormControl()
  }

And use

  <form [formGroup]="form">
  <input-overview-example controlName="control"></input-overview-example>
  </form>

  form=new FormGroup({
     control:new FormControl()
  })

See stackblitz