1

I'm not sure if there's a better way to do this, I assume there should be. Basically I have a component I want to treat as a standalone form control. This control will always have some sort of special validation attached and I would like it to bubble up to the form whenever the component is used.

I've attached a plunker. Is there a way to for the form to be marked invalid if the component/formControl is invalid? I know I could add the validator to the form itself, but I would like to make things easy and more predictable for future use of this component. I'm also open to better ideas of doing this.

@Component({
  selector: 'my-app',
  template: `
    <form [formGroup]="form">
      <my-component-control formControlName="myComponent"></my-component-control>
    </form>
    <div>Form Value: {{form.value | json}}</div>
    <div>Form Valid: {{form.valid}}</div>
  `,
})
export class App {
  constructor(fb: FormBuilder) {
    this.form = fb.group({
      myComponent: ''
    });
  }
}

@Component({
  selector: 'my-component-control',
  template: `
    <div>Control Errors: {{control.errors | json}}</div>
    <input [formControl]="control">
  `,
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      multi: true,
      useExisting: forwardRef(() => MyComponentComponent),
    }
  ]
})
export class MyComponentComponent implements ControlValueAccessor {
  control: FormControl;

  onChange: any = () => { };
  onTouched: any = () => { };

  constructor() {
    this.control = new FormControl('', (control) => {
      return control.value ? null : {shouldHaveSomething: true};
    });
    this.control.valueChanges.subscribe((value) => {
      this.onChange(value);
      this.onTouched();
    });
  }

  writeValue (obj: any): void {
    this.control.setValue(obj);
  }
  registerOnChange (fn: any): void {
    this.onChange = fn;
  }
  registerOnTouched (fn: any): void {
    this.onTouched = fn;
  }
  setDisabledState?(isDisabled: boolean): void {
    throw new Error('Method not implemented.');
  }
}
6
  • take a look at angular/material's approach. They separate the form fields from the mat-error directive: github.com/angular/material2/blob/… Commented Mar 9, 2018 at 18:36
  • @masterfloda Thanks for the info. I have something similar for showing the control has an error itself, but what I really need it the control to influence the valid property on the form itself as I don't want ngSubmit to be triggerable until the form is actually valid. Does that make sense? Commented Mar 9, 2018 at 18:42
  • Yeah, but I would not make validation part of the field component. Just use Angular's form validation angular.io/guide/form-validation#reactive-form-validation . Or do I somehow miss the point here? Commented Mar 9, 2018 at 18:49
  • I can do that. I was just trying to avoid it because this component it going to be used in several places and I know one of these days I'm going to forget to add the validator to the form. I was just hoping there was a way to make it ALWAYS validate for this component. Commented Mar 9, 2018 at 19:05
  • That makes sense... I haven't tried this myself, so it's just a guess, but you could use the setError method of FormControl angular.io/api/forms/AbstractControl#setValue Commented Mar 9, 2018 at 19:29

1 Answer 1

2

One solution could be using setValidators method on host AbstractControl

To do so I'm going to get reference to AbstractControl through NgControl. We can't just inject NgControl in constructor because we'll get issue with instantiating cyclic dependency.

constructor(private injector: Injector) {
  ...     
}

ngOnInit() {
  Promise.resolve().then(() => {
    const hostControl = this.injector.get(NgControl, null);
    if (hostControl) {
      hostControl.control.setValidators(this.control.validator);
      hostControl.control.updateValueAndValidity();
    }
  }); 
}

Ng-run Example

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

1 Comment

This is basically exactly what I was looking for. Thanks.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.