angularangular-materialcustom-controlscontrolvalueaccessor

How to wrap custom Angular Material control into own component?


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.


Solution

  • 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