Gav_at_HRSTS
Gav_at_HRSTS

Reputation: 189

Reactive Form Components

We use many Angular material fields and they are all presented the same. I want a way to simplify any changes coming from UI designers and maintenance later.

How can I create a component that reduces this block of HTML (just example code)

<div fxFlex>
  <mat-form-field>
      <mat-label>The Label</mat-label>
       <input matInput formControlName="controlName" ... >
       <mat-hint>*Required</mat-hint> 
       <mat-error *ngIf="controlName.invalid && controlName.touched">{{errorMessage}}</mat-error>
   </mat-form-field>
<div>

down to something sensible like this

<custominput  formControlName="controlName" [errorMessage]="Error" ...></custominput>

I've tried to follow along with these tutorials without success.

Angular Custom Fields Blog

Angular material Custom Fields

I really don't understand how to apply to an input field essentially. This is what I'm left with at present.

TS file contents

import { Component, ChangeDetectionStrategy, Input, OnInit, ViewChild } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { MatInput } from '@angular/material/input';

@Component({
  selector: 'hr-textinput',
  template:`
    <div [ngClass]="labelPosition">
      <mat-label>{{label}}</mat-label>
      <mat-form-field>
          <input #inputFieldIdentifier matInput formControlName={{controlName}} id={{id}} (blur)="onTouched()"> 
          <mat-error *ngIf="controlName.invalid">{{errorMsg}}</mat-error> 
          <mat-hint  *ngIf="mandatory">*Required</mat-hint>
      </mat-form-field>
    </div>
  `,
  styleUrls: ['./textinput.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      multi:true,
      useExisting: TextinputComponent
    }
  ]
})

export class TextinputComponent implements ControlValueAccessor, OnInit {
  @ViewChild('inputFieldIdentifier') inputField: MatInput;

  @Input() controlName:  string = "";
  @Input() mandatory: boolean = false;
  @Input() label: string = "";
  @Input() errorMsg = "";                     
  
  @Input() labelPosition: string = "before";  // "before" and  "above"  are the two options available at present
  @Input() id: string = "";                   

  touched = false;
  disabled = false;

  constructor() { 
    
  }
  ngOnInit(): void {
    this.id = `${this.controlName}_ID`;
  }

  onChange = (value) => {};
  onTouched = () => {};

  writeValue(fieldValue: any): void {
    this.inputField.value = fieldValue;
  }
  registerOnChange(onChangeFn: any): void {
    this.onChange = onChangeFn;
  }
  registerOnTouched(onTouchedFn: any): void {
    this.onTouched = onTouchedFn;
  }
  setDisabledState?(isDisabled: boolean): void {
    this.inputField.disabled = isDisabled;
  }
  markAsTouched() {
    if (!this.touched) {
      this.onTouched();
      this.touched = true;
    }
  }
}

SCSS file contents

.above {
  display: flex;
  flex-direction: column;
  justify-content: space-between;  
  align-items: left;
  .mat-form-field {
    padding-top: 6px;  
  }
}

.before {
  display: flex;
  flex-direction: row;
  justify-content: space-between;  
  align-items: center;
  .mat-form-field {
    padding-left: 12px;  
  }
}

Any thoughts are greatly appreciated as I've clearly no idea.

StackBlitz Demo of Issue

Upvotes: 0

Views: 267

Answers (1)

g0rb
g0rb

Reputation: 2379

The reason you are seeing the The app cannot find my 'form' error is because you are using the formControlName directive inside the component that is supposed to be implementing the formControlName interface. If you remove the formControlName directive from your custom input component template, you will no longer see that error.

In other words, when implementing ControlValueAccessor you are essentially telling Angular that your component knows how to use the formControlName directive and so the formControlName directive should be put on your custom component when instantiating it. i.e.

<hr-textinput formControlName="myField">
</hr-textinput>

I forked your stackblitz to show a working form: https://stackblitz.com/edit/angular-ivy-onycez?

Upvotes: 1

Related Questions