0

I am using a custom component (custom-text) which has the below code

    @Component({
      selector: 'custom-text, [custom-text]',
      templateUrl: './custom-text.template.html',
      styleUrls: ['./custom-text.component.scss']
    })
    export class CustomTextComponent implements OnInit {


      constructor() { }

      ngOnInit() {
      }
    }

Inside the custom-text.template.html

    <mat-form-field>
      <input matInput 
            id="controlid" 
            name="controlname" 
            maxlength="8"
            [(ngModel)]="value">
    </mat-form-field>

When I include this control inside a form (template-driven) on another component.

    <form #someForm="ngForm">
        <div custom-text></div>
    </form>

or 

<form #someForm="ngForm">
    <custom-text></custom-text>
</form>

I am unable to get the instance of the control using someForm.controls['controlId']

What am i doing wrong.

4
  • 2
    A Custom from control must be implements ControlValueAccessor, see e.g. stackoverflow.com/questions/40009149/… Commented Apr 26, 2019 at 6:38
  • @Eliseo Thank you for helping me with this, however, when I am using this with a mat-form-field it does not work as expected, the parent form does not consider this control as part of it. Commented Apr 26, 2019 at 7:18
  • I put below how make a custom control validators based in a input mat Commented Apr 26, 2019 at 9:44
  • I add another response not using a custom form control. In the stackblitz you has the two options Commented Apr 26, 2019 at 10:08

2 Answers 2

1

I leave in stackblitz the most simple custom form control based in material input.

As you see implements ControlValueAccessor, that's has the functions:

onChange:any; //declare this function to indicate in any time that you change the value
onTouched:any; //declare this function to indicate in any time that your compoment is touched

 writeValue(value: any[]|any): void {
    this.value=value;
  }

  registerOnChange(fn: any): void {
    this.onChange = fn;
  }

  registerOnTouched(fn: any): void {
    this.onTouched = fn;
  }

  setDisabledState(isDisabled: boolean): void {
    this.disabled=isDisabled
  }

And a provider like

   {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => CustomMat),
      multi: true
    }

If you want to make a validation inside the component, you need add a new provider

   {
      provide: NG_VALIDATORS,
      useExisting: forwardRef(() => CustomMat),
      multi: true,
    }

And create the function validate

validate(control: AbstractControl): ValidationErrors | null{
      //your logic here
      return null;
  }

I used this two more functions:

setValue(value: any){
    this.value=value;
    this.onChange(value);

  }
  focusOut()
  {
    this.onTouched()
  }

To call a change and a touched functions

Updated 01.04.20 well, the problem of this custom material input is that the error can not see reflected in the input, so we are going to make some change

The idea is add a customError matcher

export class CustomFieldErrorMatcher implements ErrorStateMatcher {
  constructor(private customControl: AbstractControl,private errors:any) { }

  isErrorState(control: AbstractControl | null, form: FormGroupDirective | NgForm | null): boolean {
    return this.customControl && this.customControl.touched && this.customControl.invalid;
  }
}

That's our inner input is invalid if the control is invalid. Well, are a dificult that is know what is the this "control". For this, in ngAfterViewInit we inject the NgControl, and this ngControl will be our control, puff

ngAfterViewInit(): void {
    const ngControl: NgControl = this.injector.get(NgControl, null);
    if (ngControl) {
      setTimeout(() => {
        this.control = ngControl.control;
         this.matcher = new CustomFieldErrorMatcher(this.control,null);
      })
    }
  }

At least add this matcher

  <input name="input" matInput [ngModel]="value"
    (ngModelChange)="setValue($event)" 
    [errorStateMatcher]="matcher" >

You can see in this stackblitz

Sign up to request clarification or add additional context in comments.

2 Comments

injecting the ngControl is the best "puff" I saw. With this my components are now compatible with template driven forms AND react form controls. The only thing I don't understand is why we need the setTimeout()?
it's only to avoid the initial error: Error: ExpressionChangedAfterItHasBeenCheckedError
0

thinking about your problem really you needn't make a custom form component, just a component that pass as input the form control. Just add an Input

@Input() control
//And in .html
<mat-form-field>
      <input matInput 
            id="controlid" 
            name="controlname" 
            maxlength="8"
            [formControl]="control">
</mat-form-field>

You use the component like

<form #someForm="ngForm">
    <div custom-text [control]="someForm.controls['controlId']"></div>
</form>

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.