Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to compose Angular Material form control components

The code below shows an Autocomplete form control that allows selection of a US state.

  <mat-form-field class="example-full-width">     <input matInput placeholder="State" aria-label="State" [matAutocomplete]="auto" [formControl]="stateCtrl">     <mat-autocomplete #auto="matAutocomplete">       <mat-option *ngFor="let state of filteredStates | async" [value]="state.name">         <img style="vertical-align:middle;" aria-hidden src="{{state.flag}}" height="25" />         <span>{{ state.name }}</span> |         <small>Population: {{state.population}}</small>       </mat-option>     </mat-autocomplete>   </mat-form-field> 

However, if in my application I have numerous locations where this type of input is required then it would make sense to turn this into a component (directive?) where all the boilerplate does not need to be repeated. However, I would still like to be able to use this in either template-driven or model-driven forms and allow the placeholders, validations etc. to be varied by the container component.

What is a simple and robust way to achieve this?

I have tried general approaches recommended for Angular but they do not take account of the various requirements of Angular Material. E.g. Needing to implement MatFormFieldControl. The guidance provided by Angular Material is directed more at creating a new form control using primitive elements rather than utilising/wrapping existing Angular Material form controls.

The objective is to be able to do something like this in a form:

<mat-form-field>     <lookup-state placeholder="State of Residence" required="true" formControlName="resState">     </lookup-state> </mat-form-field> 
like image 479
Phil Degenhardt Avatar asked Jan 12 '18 00:01

Phil Degenhardt


People also ask

What is MatFormFieldControl?

MatFormFieldControl. An interface which allows a control to work inside of a MatFormField .

What is matSuffix?

html - matSuffix makes mat-icon not horizontally centered in mat-icon-button - Stack Overflow.

What is Mat label?

mat-label is similar to labels which we use in normal HTML forms. But the advantage with mat-label is that it has pre-defined CSS style classes and animations defined in it.


1 Answers

i'm going to paste my example of component using Angular Material. I've created a custom Input component (two cases: simple input or autocomplete):

this is my Input.component.html

<mat-form-field color="accent" [hideRequiredMarker]="true" [class.mat-form-field-invalid]="hasErrors">    <ng-container *ngIf="autocomplete">      <input matInput [matAutocomplete]="auto" [type]="type" [placeholder]="placeholder" [disabled]="isDisabled" [value]="innerValue" (input)="autocompleteHandler($event)" (blur)="autocompleteBlur($event)">      <mat-autocomplete #auto [displayWith]="displayText" (optionSelected)="updateOption($event)">        <mat-option *ngFor="let choice of autocompleteChoices | async" [value]="choice">{{ choice.text }}</mat-option>      </mat-autocomplete>    </ng-container>    <input *ngIf="!autocomplete" matInput [type]="type" [placeholder]="placeholder" [disabled]="isDisabled" [value]="innerValue" (input)="inputHandler($event)" (blur)="setTouched()">  </mat-form-field>

this is my Input.component.ts

import { Component, Input, forwardRef } from '@angular/core';  import { NG_VALUE_ACCESSOR, ControlValueAccessor, NgModel } from '@angular/forms';  import { MatAutocompleteSelectedEvent } from '@angular/material';    import { ChoiceList } from '../../../../models/choice-list';  import { ChoiceSource } from '../../../../models/choice-source';  import { getFlagAttribute } from '../../../../utils';  import { HintComponent } from '../hint/hint.component';  import { ErrorsComponent } from '../errors/errors.component';  import { FormField } from '../form-field';  import { ChoiceModel } from '../../../../models/choice-model';  import { Observable } from 'rxjs/Observable';  import 'rxjs/add/operator/toPromise';    @Component({    selector: 'my-input',    templateUrl: './input.component.html',    styleUrls: ['./input.component.scss'],    providers: [{      provide: NG_VALUE_ACCESSOR,      useExisting: forwardRef(() => InputComponent),      multi: true    }]  })  export class InputComponent extends FormField implements ControlValueAccessor {    @Input() type = 'text';    @Input() placeholder: string;    @Input() autocomplete: ChoiceSource;      autocompleteChoices: ChoiceList;      @Input() set value(value: string) {      this.innerValue = value == null ? '' : String(value);    }    get value() {      return this.innerValue;    }      @Input() set disabled(value: any) {      this.setDisabledState(getFlagAttribute(value));    }    get disabled() {      return this.isDisabled;    }      private changeCallback: Function;    private touchedCallback: Function;      isDisabled = false;    innerValue = '';      displayText(value: ChoiceModel): string {      return value.text;    }      writeValue(value: any) {      if (!this.autocomplete) {        this.value = value;      }    }    registerOnChange(fn: Function) {      this.changeCallback = fn;    }    registerOnTouched(fn: Function) {      this.touchedCallback = fn;    }    setDisabledState(isDisabled: boolean) {      this.isDisabled = isDisabled;    }      inputHandler(event: Event) {      this.value = (<HTMLInputElement>event.target).value;      if (this.changeCallback) {        this.changeCallback(this.value);      }    }      autocompleteHandler(event: Event) {      const text = (<HTMLInputElement>event.target).value;      if (this.autocomplete) {        if (text) {          this.autocompleteChoices = this.autocomplete(text);        } else if (this.changeCallback) {          this.innerValue = '';          this.changeCallback(null);        }      }    }      autocompleteBlur(event: Event) {      (<HTMLInputElement>event.target).value = this.innerValue;      this.setTouched();    }      updateOption(event: MatAutocompleteSelectedEvent) {      if (this.changeCallback) {        const { value, text } = event.option.value;        this.value = text;        this.changeCallback(value);      }    }      setTouched() {      if (this.touchedCallback) {        this.touchedCallback();      }    }  }

Now i'll going to put an example of using both of them:

simple input case

<my-input type="text" name="myInputName" [(ngModel)]="myNgModel" placeholder="---" required pattern="[a-zA-Zàèìòù\'\s0-9\.]+">  </my-input>

autocomplete input case

export myClass implements OnInit, AfterViewInit, ControlValueAccessor, AfterViewChecked {    @ViewChild('BirthTown') BirthTown: InputComponent; //from import    public autocompleteSourceBirthTown: Function;    this.autocompleteSourceBirthTown = (async function(input: string) {        if (input.trim().length > 2) {          const towns = await this.generalService.getListBirthTowns(input.trim());          return towns;        }        return [];      }).bind(this);            // only for text of town  ngAfterViewChecked() {      if (this.BirthTown && this.BirthTownNgModel) {        const textTown = this.stateService.getDataBirthTown(this.BirthTownNgModel);        if (textTown) {          this.textBirthTown = textTown;        }      }
<seg-input #BirthTown [(ngModel)]="BirthTownNgModel" placeholder="BirthTown"  [autocomplete]="autocompleteSourceBirthTown" [value]="textBirthTown" required>  </seg-input>

hope will help

like image 92
Luca Taccagni Avatar answered Sep 20 '22 06:09

Luca Taccagni