Capture an instance of FormControl by requesting a component template

I have a custom FormFieldComponent that encapsulates the HTML display logic and errors for the form field:

 @Component({ selector: 'field', template: ` <div class="form-group"> <label class="control-label">{{label}}</label> <ng-content></ng-content> <!-- Will display the field --> <!-- Here, put error display logic --> </div> ` }) export class FormFieldComponent { @Input() label: string; // Field label @Input() theControl: FormControl; // Current form control, required to display errors } 

In FormFieldComponent I need an instance of FormControl to display errors.

My form is as follows:

 <form [formGroup]="myForm"> ... <field label="Title" [theControl]="myForm.get('title')"> <input type="text" formControlName="title"> </field> ... </form> 

But I'm not quite happy with the code above. As you can see, I specify the field key in two places: in the input property [theControl] and in the directive formControlName .

The code would be more concise if I could just write:

 <field label="Title"> <input type="text" formControlName="title"> </field> 

Note that the input property [theControl] missing. FieldComponent should be able to get the instance of FormControl that it contains, but how?

I tried using the @ContentChildren decorator to request a component template for FormControl directives, but it does not work:

 export class FormFieldComponent { @ContentChildren(FormControlDirective) theControl: any; } 

Another option is to pass the field key as input to the FormFieldComponent , and then allow the component to use this key for:

  • Programmatically apply the formControlName directive to the field it contains.
  • Get your parent <form> , access the corresponding FormGroup instance, and extract a FormControl instance from it.
+5
source share
2 answers

short answer: you cannot

You just can't. (Well, maybe you can, but it will be hacks!)

long answer: you cannot, but ...

FormControl not FormControl . Directives are injected, but you have to deal with formControlName , ngModel , FormControl , etc., And they will not be accessible from the packaging component, but its children ...

In your case, you can try with @ContentChildren(FormControlName) theControl: any; , since your code FormControlDirective not have a FormControlDirective , but you still cannot access the FormControl (the _control property is internal, soo be a hack) ...

So, you should stick to your mistakes from the component associated with FormGroup .

BUT , if you want to display user input (which will not display the error message as is, but will be able to show that this input is in an error state (the host element will receive ng-valid , ng-invalid , so this is just a style question), you can do this by executing ControlValueAccessor .

The bridge between the control and its own element.

A ControlValueAccessor abstracts the operation of writing a new value to a DOM element that represents an input control.

This means that directives / components implementing this interface can be used with ngModel , FormControl , etc.

for example: <my-component [(ngModel)]="foo"></my-component>

this is not an exact reproduction of your problem, but this implementation solved the same problem for me:

 export const INPUT_VALUE_ACCESSOR: any = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => InputComponent), multi: true }; @Component({ selector: "field", template: `<!--put anything you want in your template--> <label>{{label}}</label> <input #input (input)="onChange($event.target.value)" (blur)="onTouched()" type="text">`, styles: [], providers: [INPUT_VALUE_ACCESSOR] }) export class InputComponent implements ControlValueAccessor { @ViewChild("input") input: ElementRef; @Input() label:string; onChange = (_: any) => { }; onTouched = () => { }; constructor(private _renderer: Renderer) { } writeValue(value: any): void { const normalizedValue = value == null ? "" : value; this._renderer.setElementProperty(this.input.nativeElement, "value", normalizedValue); } registerOnChange(fn: (_: any) => void): void { this.onChange = fn; } registerOnTouched(fn: () => void): void { this.onTouched = fn; } setDisabledState(isDisabled: boolean): void { this._renderer.setElementProperty(this.input.nativeElement, "disabled", isDisabled); } } 

then you can simply:

 <field label="Title" formControlName="title"></field> 
+3
source

You can get an instance of the format control instance through:

 @Component({ selector: 'field', templateUrl: './field.component.html', styleUrls: ['./field.component.scss'] }) export class FieldComponent implements AfterContentInit { @Input() public label: string; @ContentChild(FormControlName) public controlName: FormControlName; public ngAfterContentInit(): void { console.log(this.controlName.control); } } 
0
source

Source: https://habr.com/ru/post/1259961/


All Articles