Search code examples
angularangular-reactive-formsangular-components

Access FormControl inside custom Angular Component


I'm creating a custom angular component which shows an error tooltip when my FormControl (Reactive Forms) is invalid. But I don't know how I can access the FormControl inside my custom component to check whether or not it is marked as valid.

What I want to accomplish

<div [formGroup]="form">
     <input formControlName="name" type="text" />
     <custom-validation-message formControlName="name">My special error message!</custom-validation-message>
  </div>

Already encountered stuff

ERROR Error: No value accessor for form control with name: 'surveyType'

Fixed this by implementing ControlValueAccessor with NG_VALUE_ACCESSOR even though I don't want to alter the value. I also added an injector to access the NgControl.

import { Component, OnInit, Injector } from '@angular/core';
import { NgControl, ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

@Component({
    selector: 'custom-validation-message',
    templateUrl: './validation-message.component.html',
    providers: [{
        provide: NG_VALUE_ACCESSOR, multi: true, useExisting: ValidationMessageComponent
    }]
})
export class ValidationMessageComponent implements ControlValueAccessor, OnInit {
    public formControl: any;

    constructor(private injector: Injector) {
        super();
    }

    public ngOnInit(): void {
        const model = this.injector.get(NgControl);
        this.formControl = model.control;
    }

    public writeValue(obj: any): void {
    }
    public registerOnChange(fn: any): void {
    }
    public registerOnTouched(fn: any): void {
    }
    public setDisabledState?(isDisabled: boolean): void {
    }
}

Current Problem The model.control is undefined. After inspecting the model I found out that the model is as good as empty only the _parent is a full representation of my form. The model._parent.controls does contain all the controls of my form. But still I don't know the current control.


Solution

  • as I get you point you just want to make a componnet for display form control validation message the other answer explane why ControlValueAccessor is not the case here ,you just want to pass a control form reference to the component then check the validation state , Thomas Schneiter answer is a correct why but I face the case and it 's hard to keep get refrance by get method and sometime we are in sub group and form array so I idea is to just pass the name of the form control as string then get the control reference.

    CustomValidationMessageComponent

    @Component({
      selector: "custom-validation-message",
      templateUrl: "./custom-validation-message.component.html",
      styleUrls: ["./custom-validation-message.component.css"]
    })
    export class CustomValidationMessageComponent {
      @Input()
      public controlName: string;
    
      constructor(@Optional() private controlContainer: ControlContainer) {} 
    
      get form(): FormGroup {
        return this.controlContainer.control as FormGroup;
      }
    
      get control(): FormControl {
        return this.form.get(this.controlName) as FormControl;
      }
    }
    

    template

    <ng-container *ngIf="control && control?.invalid && control?.touched">
      <ul>
        <li *ngIf="control.hasError('required')">
          this is required field
        </li>
        <li *ngIf="control.hasError('pattern')">
          pattern is invalid 
        </li>
        <li *ngIf="control.hasError('maxlength')">
          the length is over the max limit
        </li>
         <!-- <li *ngFor="let err of control.errors | keyvalue">
           {{err.key}}
         </li> -->
      </ul>
    
    </ng-container>
    

    and you can use it like this

    <form [formGroup]="form">
     <label>
       First Name <input type="text" formControlName="firstName" />
       <div>
           <custom-validation-message controlName="firstName"></custom-validation-message>
       </div>
     </label>
    
     ...
      
    </form>
    

    demo

    you can check this angular library ngx-valdemort created by JB Nizet where it solve this problem perfectly.