2
votes

I have to create a component with custom input element (and more elements inside the component, but its not the problem and not part of the example here) with reactive / model driven approach and validation inside and outside the component.

I already created the component, it works fine, my problem is that both formControl's (inside child and parent) are not in sync when it comes to validation or states like touched. For example if you type in a string with more then 10 characters, the form control inside the form is stil valid.

Plunkr

//our root app component
import {Component, Input} from '@angular/core'
import { 
  FormControl,
  FormGroup,
  ControlValueAccessor,
  NG_VALUE_ACCESSOR,
  Validators
} from '@angular/forms';


@Component({
  selector: 'my-child',
  template: `

  <h1>Child</h1>
  <input [formControl]="childControl">
  `,
  providers: [
    {provide: NG_VALUE_ACCESSOR, useExisting: Child, multi: true}
  ]
})

export class Child implements ControlValueAccessor {
  childControl = new FormControl('', Validators.maxLength(10));

  writeValue(value: any) {
    this.childControl.setValue(value);
  }

  registerOnChange(fn: (value: any) => void) {
    this.childControl.valueChanges.subscribe(fn);
  }

  registerOnTouched() {}
}


@Component({
  selector: 'my-app',
  template: `
    <div>
      <h4>Hello {{name}}</h4>
      <form [formGroup]="form" (ngSubmit)="sayHello()">
       <my-child formControlName="username"></my-child>
       <button type="submit">Register</button>
      </form>
      {{form.value | json }}
    </div>
  `
})
export class App {

  form = new FormGroup({
    username: new FormControl('username', Validators.required)
  });

  constructor() {
    this.name = 'Angular2';
  }

  sayHello() {
    console.log(this.form.controls['username'])
  }

}

I have no clue how to solve this problem in a proper way

1
Try using @Input() inside the child, the parent will give the value to the child and the child will process the information and validate it. Something like @Input() valuefromparent; childControl = new FormControl(this.valuefromparent, Validators.maxLength(10));Avram Virgil
Tried your suggestion, but it stil does not work as desired: plnkr.co/edit/YaFPU0mPolnnYPuJ0rmi?p=previewuser3287019
I think I get what you are trying to do now, well the child understands the value and validates it the problem is the parent has no idea about what the child has done, you will need to trigger an event emmiter from the child to the parent.Avram Virgil
With EventEmitter I can achieve that my parent knows about validation. The problem in my case is also that my child does not know about FormControl on my parent. Do you have an idea how can I access to the FormControl of parent in child?user3287019
You're aware you're instantiating two controls? The reason they're not in sync is because they're not the same object.silentsod

1 Answers

0
votes

There exists a Validator interface from Angular to forward the validation to the parent. You need to use it and provide NG_VALIDATORS to the component decorator's providers array with a forwardRef:

{
  provide: NG_VALIDATORS,
  useExisting: CustomInputComponent,
  multi: true
}

your component needs to implement the Validator interface:

class CustomInputComponent implements ControlValueAccessor, Validator,...

and you have to provide implementations of the Validator interfaces' methods, at least of the validate method:

  validate(control: AbstractControl): ValidationErrors | null {
    return this.formGroup.controls['anyControl'].invalid ? { 'anyControlInvalid': true } : null;
  } 

there is also another to sync the validators when their inputs change:

  registerOnValidatorChange(fn: () => void): void {
    this.validatorChange = fn;
  }