angularangular-formlyngx-formly

Styling Formly Form Custom Wrapper + Custom Type


So I'd like to have a mat-autocomplete using formly-forms and have a add and delete button at the end of each autocomplete. Most importantly I want this "duo" of autocomplete + button to take 100% of the width available. To get there, I created a custom type for the autocomplete which works and a wrapper to add the button (for the sake of the example I'm only using a "delete" button for now).

I'm facing 2 major challenges:

  1. One, the mat-form-field styling get messed-up when using the wrapper with the custom type. I have a fix for that which is to delete the "form-field" wrapper declaration with the declaration of the custom type in the formly config and adding this "form-field" manually with a tag directly in the autocomplete custom type.
  2. If I do the previous, the width gets always messed up and the "duo" either takes only about 33% of the available width for some reason OR (if I apply a width of 100% styling in the ) the button gets pushed to the next line which is not what I'm expecting.

Here's a stackblitz with a reproduction of the problem (forked from Formly autocomplete example directly).

And the code directly:

App.component.html:

<form [formGroup]="form">
  <formly-form [model]="model" [fields]="fields" [options]="options" [form]="form"></formly-form>
</form>


<!-- Copyright 2021 Formly. All Rights Reserved.
    Use of this source code is governed by an MIT-style license that
    can be found in the LICENSE file at https://github.com/ngx-formly/ngx-formly/blob/main/LICENSE -->

app.component.ts

import { Component } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { FormlyFormOptions, FormlyFieldConfig } from '@ngx-formly/core';
import { of } from 'rxjs';

const states = [
  'Alabama',
  'Alaska',
  'American Samoa',
  'Arizona',
  'Arkansas',
  'California',
  'Colorado',
  'Connecticut',
  'Delaware',
  'District Of Columbia',
  'Federated States Of Micronesia',
  'Florida',
  'Georgia',
  'Guam',
  'Hawaii',
  'Idaho',
  'Illinois',
  'Indiana',
  'Iowa',
  'Kansas',
  'Kentucky',
  'Louisiana',
  'Maine',
  'Marshall Islands',
  'Maryland',
  'Massachusetts',
  'Michigan',
  'Minnesota',
  'Mississippi',
  'Missouri',
  'Montana',
  'Nebraska',
  'Nevada',
  'New Hampshire',
  'New Jersey',
  'New Mexico',
  'New York',
  'North Carolina',
  'North Dakota',
  'Northern Mariana Islands',
  'Ohio',
  'Oklahoma',
  'Oregon',
  'Palau',
  'Pennsylvania',
  'Puerto Rico',
  'Rhode Island',
  'South Carolina',
  'South Dakota',
  'Tennessee',
  'Texas',
  'Utah',
  'Vermont',
  'Virgin Islands',
  'Virginia',
  'Washington',
  'West Virginia',
  'Wisconsin',
  'Wyoming',
];

@Component({
  selector: 'formly-app-example',
  templateUrl: './app.component.html',
})
export class AppComponent {
  form = new FormGroup({});
  model: any = {};
  options: FormlyFormOptions = {};
  fields: FormlyFieldConfig[] = [
    {
      key: 'Autocomplete',
      type: 'autocomplete',
      wrappers: ['buttonWrapper'],
      props: {
        required: true,
        label: 'Autocomplete',
        placeholder: 'Placeholder',
        filter: (term: string) =>
          of(term ? this.filterStates(term) : states.slice()),
      },
    },
  ];

  filterStates(name: string) {
    return states.filter(
      (state) => state.toLowerCase().indexOf(name.toLowerCase()) === 0
    );
  }
}

/**  Copyright 2021 Formly. All Rights Reserved.
    Use of this source code is governed by an MIT-style license that
    can be found in the LICENSE file at https://github.com/ngx-formly/ngx-formly/blob/main/LICENSE */

app.module.ts

import { NgModule } from '@angular/core';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ReactiveFormsModule } from '@angular/forms';
import { FormlyModule } from '@ngx-formly/core';
import { FormlyMaterialModule } from '@ngx-formly/material';

import { MatInputModule } from '@angular/material/input';
import { MatAutocompleteModule } from '@angular/material/autocomplete';

import { AppComponent } from './app.component';
import { AutocompleteTypeComponent } from './autocomplete-type.component';
import { AutcompleteButtonWrapperComponent } from './custom-wrapper.component';
import { MatIconModule } from '@angular/material/icon';
import { MatButtonModule } from '@angular/material/button';

@NgModule({
  imports: [
    BrowserAnimationsModule,
    ReactiveFormsModule,
    MatInputModule,
    MatButtonModule,
    MatIconModule,
    MatAutocompleteModule,
    FormlyMaterialModule,
    FormlyModule.forRoot({
      types: [
        {
          name: 'autocomplete',
          component: AutocompleteTypeComponent,
          wrappers: ['form-field'], // Comment this line and comment out the mat-form-field tag in the autocomplete type to adjust the styling;
        },
      ],
      wrappers: [
        {
          name: 'buttonWrapper',
          component: AutcompleteButtonWrapperComponent,
        },
      ],
      validationMessages: [
        { name: 'required', message: 'This field is required' },
      ],
    }),
  ],
  bootstrap: [AppComponent],
  declarations: [
    AutcompleteButtonWrapperComponent,
    AutocompleteTypeComponent,
    AppComponent,
  ],
})
export class AppModule {}

/**  Copyright 2021 Formly. All Rights Reserved.
    Use of this source code is governed by an MIT-style license that
    can be found in the LICENSE file at https://github.com/ngx-formly/ngx-formly/blob/main/LICENSE */

autocomplete-type.component.ts

import { NgModule } from '@angular/core';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ReactiveFormsModule } from '@angular/forms';
import { FormlyModule } from '@ngx-formly/core';
import { FormlyMaterialModule } from '@ngx-formly/material';

import { MatInputModule } from '@angular/material/input';
import { MatAutocompleteModule } from '@angular/material/autocomplete';

import { AppComponent } from './app.component';
import { AutocompleteTypeComponent } from './autocomplete-type.component';
import { AutcompleteButtonWrapperComponent } from './custom-wrapper.component';
import { MatIconModule } from '@angular/material/icon';
import { MatButtonModule } from '@angular/material/button';

@NgModule({
  imports: [
    BrowserAnimationsModule,
    ReactiveFormsModule,
    MatInputModule,
    MatButtonModule,
    MatIconModule,
    MatAutocompleteModule,
    FormlyMaterialModule,
    FormlyModule.forRoot({
      types: [
        {
          name: 'autocomplete',
          component: AutocompleteTypeComponent,
          wrappers: ['form-field'], // Comment this line and comment out the mat-form-field tag in the autocomplete type to adjust the styling;
        },
      ],
      wrappers: [
        {
          name: 'buttonWrapper',
          component: AutcompleteButtonWrapperComponent,
        },
      ],
      validationMessages: [
        { name: 'required', message: 'This field is required' },
      ],
    }),
  ],
  bootstrap: [AppComponent],
  declarations: [
    AutcompleteButtonWrapperComponent,
    AutocompleteTypeComponent,
    AppComponent,
  ],
})
export class AppModule {}

/**  Copyright 2021 Formly. All Rights Reserved.
    Use of this source code is governed by an MIT-style license that
    can be found in the LICENSE file at https://github.com/ngx-formly/ngx-formly/blob/main/LICENSE */

custom-wrapper.component.ts

import { Component } from '@angular/core';
import { FieldWrapper } from '@ngx-formly/core';

@Component({
  selector: 'formly-wrapper-button',
  template: `
    <div style="display: flex; align-items: center; width: 100%;">
      <ng-container #fieldComponent></ng-container>
      <button mat-icon-button color="warn"><mat-icon>remove</mat-icon></button>
    </div>
  `,
})
export class AutcompleteButtonWrapperComponent extends FieldWrapper {}

/**  Copyright 2021 Formly. All Rights Reserved.
    Use of this source code is governed by an MIT-style license that
    can be found in the LICENSE file at https://github.com/ngx-formly/ngx-formly/blob/main/LICENSE */

Any ideas on how to achieve this?


Solution

  • To width 100% just add in styles.css

    mat-form-field.full-width {
      width: 100%;
    }
    formly-form.full-width formly-autocomplete-type {
      width: 100%;
    }
    

    Then you use in your component

    <formly-form class="full-width" ....</formly-form>
    

    Your component shoul be add the formField

    <mat-form-field class="full-width">
      <mat-label>{{ props.label }}</mat-label>
      <input
        matInput
        [matAutocomplete]="auto"
        [formControl]="formControl"
        [formlyAttributes]="field"
        [placeholder]="props.placeholder"
        [errorStateMatcher]="errorStateMatcher"
        placeholder="Ex. Pizza"
        value="Sushi"
      />
    </mat-form-field>
    <mat-autocomplete #auto="matAutocomplete">
       ...
    </mat-autocomplete>
    

    your forked stackblitz

    NOTE: I don't know if it's better not use a formly-wrapper-button else use all the logiv in your formly-autocomplete-type, some like the entry of the blog How to auto-generate advanced forms using Formly