1

I’ve created a component (AComponent) which uses Material design and I would like to use it as formControl in my Reactive Form (in the AppComponent). I’ve implemented ControlValueAccessor in this component (AComponent).

export class AComponent implements ControlValueAccessor {
  fm = new FormControl();

  constructor(private ngControl: NgControl) {
    ngControl.valueAccessor = this;

    this.fm.valueChanges.subscribe(v => {
      this.onChange(v);
    })
  }

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

  writeValue(obj: any) {
    this.fm.setValue(obj);
  }
  registerOnChange(fn: any) {
    this.onChange = fn;
  }
  registerOnTouched(fn: any) {
    this.onTouched = fn;
  }

}

I can set Validators.required for this component (in the AppComponent)

  ngOnInit() {
    this.formGroup = this.fb.group({
      control: ['', Validators.required],
      controlOne: ['', Validators.required]
    })

    this.formGroup.statusChanges
      .subscribe(console.log);
  }

and it affects the whole form. (you can see on the console: invalid when ACompnent is invalid).

But the problem is I cannot force AComponent to look like invalid (red input) in case when it is invalid. It looks like inner formControl (in the AComponent) doesn’t get Validators.required from AppComponent.

enter image description here

The question is: How can I set validators to AComponent in an elegant way?

DEMO

1
  • Take a look at this example, doing exactly what you need (hint: search for "required" on that page). Commented Jun 11, 2019 at 19:18

3 Answers 3

1

NOTE when we create a custom form control, Angular add ng-invalid ng-touched to our component. So, in general we change the apparence of our control using .css

.ng-invalid.ng-touched
{
color:red;
}

The problem is that we want change the aparence of a mat-input inside our custom form control. So we need know when is invalid our control.

in this stackblitz you has your custom Form Control.

The key is know when your custom Form Control is invalid. For this, whe can inject ngControl. It's necesary using inject to avoid circular dependency. So, our constructor it's like

constructor(public injector: Injector) { }
  ngOnInit() {
    this.ngControl = this.injector.get(NgControl);
    this.fm.valueChanges.subscribe(v => {
      this.onChange(v);
    })
  }

Well, we know when the custom control is valid, touched,pristine...

We are going to defined a .css like

.customError,.custom
{
  display:inline-block;
}
.customError .mat-form-field-empty.mat-form-field-label {
    color: red!important;
}
.customError .mat-form-field-underline {
    background-color: red!important;
}

And use ViewEncapsulation.None in our component. ViewEncapsulation.None makes the .css are in all the aplication. It's the reason because we add the class ".customError" before .mat-form-field-empty.mat-form-field-label and mat-form-field-underline. Else ALL our mat-components looks like has error.

@Component({
   selector: 'app-a',
   templateUrl: './a.component.html',
   styleUrls: [ './a.component.css' ],
   encapsulation:ViewEncapsulation.None,
   providers: [{
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => AComponent),
    multi: true
   }]
})

After this, we use [ngClass] to add the classes

<div class="custom" 
    [ngClass]="{'customError':ngControl.invalid && ngControl.touched}" >

  <mat-form-field 
      [color]="ngControl.invalid && ngControl.touched?'warn':null" >
     <input matInput  placeholder="Some value" [formControl]="fm" (blur)="onTouched()">
  </mat-form-field>
</div>

See that we use the property [color] in the mat-form-field to make the ripple underline becomes red too and how we use (blur) to mark as touched the control

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

Comments

1

you need to provide NG_VALIDATORS

https://medium.com/@tarik.nzl/angular-2-custom-form-control-with-validation-json-input-2b4cf9bc2d73

Comments

0
providers: [
    {
        provide: NG_VALUE_ACCESSOR,
        useExisting: forwardRef(() => CustomInputComponent),
        multi: true
    },
    {
        provide: NG_VALIDATORS,
        useExisting: forwardRef(() => CustomInputComponent),
        multi: true
    }  
]

You may also opt to set the ng-error classes with @Hostbinding

Comments

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.