3

I have a custom text-area component, with text-area input inside. I have created a custom validator to check the max length (not the html one) of the text. All work fine, the problem is that the inner input is set to invalid (with ng-invalid) while che component itself don't and so also the form that contains the component remains valid. It's seems to work with the built-it required validator, placed on both the component and the input.

How can I make the changes in a custom input to be reflected on the external form?

Thanks! //sorry for my english!

Edit: I made a plunker: https://plnkr.co/edit/NHc25bo8K9OsgcxSWyds?p=preview

This is the custom text-area component html:

<textarea
  [disabled]='disabled'
  [required]='required'
  [placeholder]="placeholder"
  (changes)="onInput($event)"
  (keyup)="onInput($event)"
  [(ngModel)] = "data"
  [name]="name"
  #input="ngModel"
  customMaxLength="{{maxLength}}"
></textarea>
<span *ngIf="input.errors && (input.dirty || input.touched)">
  <span *ngIf="input.errors?.required" class="error-message">Required!</span>
  <span *ngIf="input.errors?.customMaxLength" class="error-message">Max Length Reached({{maxLength}})</span>
</span>

This is the code of the component

import { Component, Input, forwardRef, ViewChild } from '@angular/core';
import { NgModel, ControlValueAccessor, NG_VALUE_ACCESSOR, AbstractControl } from '@angular/forms';

@Component({
  selector: 'custom-text-area',
  templateUrl: './custom-text-area.component.html',
  styleUrls: ['./custom-text-area.component.less'],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => TextAreaComponent),
      multi: true
    }
  ]
})
export class TextAreaComponent implements ControlValueAccessor{

  @Input() required = false;
  @Input() name;
  @Input() data;
  @Input() disabled;
  @Input() placeholder = '';
  @Input() errorMsg;
  @Input() maxLength = null;

  @ViewChild('input') input: NgModel;
  constructor() {
    this.data = '';
  }

  propagateChange = (_: any) => {};

  writeValue(value: any): void {
    if (value !== undefined) {
      this.data = value;
    } else {
      this.data = '';
    }
  }

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

  registerOnTouched(fn: any): void {}

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

  onInput(e) {
    this.data = e.target.value || '';
    this.propagateChange(this.data);
  }
}

This is the validator

import { NG_VALIDATORS, Validator, FormControl } from '@angular/forms';
import { Directive, forwardRef, Input } from '@angular/core';

@Directive({
  selector: '[customMaxLength][ngModel]',
  providers: [
    { provide: NG_VALIDATORS, useExisting: forwardRef(() => MaxLengthValidatorDirective), multi: true}
  ]
})
export class MaxLengthValidatorDirective implements Validator{

  @Input() customMaxLength: number;

  ngOnInit(){
  }

  validate(c: FormControl): { [key: string]: any; } {
    if(c.value && this.customMaxLength){
      return c.value.length <  this.customMaxLength ? null : { customMaxLength:{ valid: false } };
    } else {
      return null;
    }
  }

}

Aaand finally this is an use:

<form #form="ngForm">
    <custom-text-area [maxLength]="3" required ngModel name="textArea"></custom-text-area>
</form>

1 Answer 1

2

The main problem is how you are using the NgModel. You are using it in both the custom component and inside your form. You should only be using it inside of your form. Meaning, textarea should not have an NgModel.

No:

<textarea
  [disabled]='disabled'
  [required]='required'
  [placeholder]="placeholder"
  (changes)="onInput($event)"
  (keyup)="onInput($event)"
  [(ngModel)] = "data"
  [name]="name"
  #input="ngModel"
  customMaxLength="{{maxLength}}"
></textarea>

Yes:

<textarea
  [disabled]='disabled'
  [required]='required'
  [placeholder]="placeholder"
  (changes)="onInput($event)"
  (keyup)="onInput($event)"
  [name]="name"
  customMaxLength="{{maxLength}}"
></textarea>

Here is a working example:

https://plnkr.co/edit/lWZpEpPdnfG7YDiH21jB?p=preview

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

1 Comment

Thank you! I have updated my plunker (plnkr.co/edit/NHc25bo8K9OsgcxSWyds?p=preview) so in this way you're able to use custom validator class without having to do all the way around like i did before, and having the error's messages inside the components (the behaviour i was looking for).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.