Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

use formControlName for custom input component in reactive form

There is a custom input component and it is used in a reactive form with validation:

@Component({
    moduleId: module.id.toString(),
    selector: 'custom-select',
    templateUrl: 'custom-select.component.html',
    styleUrls: ['custom-select.component.css']
})
export class CustomSelectComponent {
    @Input() public items: SelectModel[];
    public model: SelectModel;
    constructor(private customSelectService: CustomSelectService) {
        this.customSelectService.Selected.subscribe((data: SelectModel) => {
            this.model = data;
        });
    }
    public newSelect(select: SelectModel): void {
        this.customSelectService.updateSelected(select);
    }
}

which works fine, I am using custom-select in a reactive form and want to validate it like below:

<custom-select id="country" [items]="selectItems" formControlName="country"></custom-select>
<div *ngIf=" myFrom.controls['country'].invalid && (myFrom.controls['country'].dirty 
             || myFrom.controls['country'].touched) " class="ha-control-alert">
    <div *ngIf="myFrom.controls['country'].hasError('required')">Country is required</div>
</div>

this is how I declare the form in component

this.myFrom = this.formBuilder.group({
    country: [null, Validators.required],
})

but when I add formControlName for validations, it gets error which says No value accessor for form control with name: 'country'. How should I handle this?

like image 369
Paridokht Avatar asked Sep 10 '17 14:09

Paridokht


People also ask

What is the difference between FormControl and formControlName?

[formControl] assigns a reference to the FormControl instance you created to the FormControlDirective . formControlName assigns a string for the forms module to look up the control by name.

Can we use formControlName with ngModel?

In short ngModel can't be used by itself within FormGroup When you use formControlName, ngModel does not activate or create a control (it's simply used as an @Input). formControlName just links to the existing input you created in your class.

How do you assign a value to a reactive form?

Setting or Updating of Reactive Forms Form Control values can be done using both patchValue and setValue. However, it might be better to use patchValue in some instances. patchValue does not require all controls to be specified within the parameters in order to update/set the value of your Form Controls.


2 Answers

STEPS

  1. Add the provider for NG_VALUE_ACCESSOR in the decorator
  2. Implement the ControlValueAccessor in the class
  3. Create the onChange function like this onChange = (value: boolean) => {};
  4. Add the registerOnChange, writeValue and registerOnTouched methods of the ControlValueAccessor interface
  5. In the method that will be changed the value of select, call the onChange function passing as parameter the value of select.
        import ...
        import { Component, forwardRef } from '@angular/core';
        import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
        
    
        @Component({
            moduleId: module.id.toString(),
            selector: 'custom-select',
            templateUrl: 'custom-select.component.html',
            styleUrls: ['custom-select.component.css'],
            // STEP 1
            providers: [{
              provide: NG_VALUE_ACCESSOR,
              multi: true,
              useExisting: forwardRef(() => CustomSelectComponent)
            }]
        })
        // STEP 2
        export class CustomSelectComponent implements ControlValueAccessor {
            // STEP 3
            onChange = (value: SelectModel) => {};
            @Input() public items: SelectModel[];
            public model: SelectModel;
            constructor(private customSelectService: CustomSelectService) {
                this.customSelectService.Selected.subscribe((data: SelectModel) => {
                    this.model = data;
                });
            }
            public newSelect(select: SelectModel): void {
                // STEP 5
                this.onChange(select);
                this.customSelectService.updateSelected(select);
            }
            // STEP 4
            registerOnChange(fn: (value: SelectModel) => void): void {
                this.onChange = fn;
            }
            writeValue() {}
            registerOnTouched(){}
        }

Don't forget to add the formControlName in the selector.

like image 170
Julio Cesar Brito Gomes Avatar answered Oct 13 '22 01:10

Julio Cesar Brito Gomes


Add this to the providers of the child:

viewProviders: [{
  provide: ControlContainer, 
  useExisting: FormGroupDirective 
}]

The reason it fails in the first place is it doesn't look in the parent scope for FormGroup, so this just passes it down to the child.

Then you need to trick it into thinking your control has a valueAccessor - which it doesn't need because you just created a sort of 'shim'.

export const NOOP_VALUE_ACCESSOR: ControlValueAccessor = 
{
    writeValue(): void {},
    registerOnChange(): void {},
    registerOnTouched(): void {}
};

Then in the constructor for your child:

constructor(@Self() @Optional() public ngControl: NgControl) 
{
    if (this.ngControl)
    {
        this.ngControl.valueAccessor = NOOP_VALUE_ACCESSOR;
    }
}

Then you can use the formControlName as normal inside the child.

If you need to pass the formControlName from the parent component (which you probably would if it was meant to be a reusable control) just create a string @Input property and do this in the child:

    <input matInput [placeholder]="desc" 
           [formControlName]="formControlName">
like image 21
Simon_Weaver Avatar answered Oct 13 '22 01:10

Simon_Weaver