I have a custom FormFieldComponent
that encapsulates the HTML and error display logic for a 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 the FormControl to display errors.
My form then looks like this:
<form [formGroup]="myForm">
...
<field label="Title" [theControl]="myForm.get('title')">
<input type="text" formControlName="title">
</field>
...
</form>
But I'm not entirely satisfied with the above code. As you can see, I am specifying the field's key in two locations: in the [theControl]
input property and in the formControlName
directive.
The code would be more concise if I could just write:
<field label="Title">
<input type="text" formControlName="title">
</field>
Notice how the [theControl]
input property is gone. The FieldComponent
should be able to get a hold of the FormControl instance it contains, but how?
I have tried using the @ContentChildren
decorator to query the component's template for FormControl directives, but it doesn't work:
export class FormFieldComponent {
@ContentChildren(FormControlDirective) theControl: any;
}
Another option would be to pass the field's key as an input to FormFieldComponent
and then let the component use that key to:
formControlName
directive to the field it contains.<form>
, access the corresponding FormGroup instance, and extract the FormControl instance from that.To fetch the value of a form control, we have to use value property on the instance of FormControl in our class. In the same way we can fetch the value in HTML template. city = new FormControl('Noida'); console.
In Angular, a reactive form is a FormGroup that is made up of FormControls. The FormBuilder is the class that is used to create both FormGroups and FormControls.
What is a form group in Angular? Form groups wrap a collection of form controls. Just as the control gives you access to the state of an element, the group gives the same access but to the state of the wrapped controls.
You just can't. (Well, maybe be you can but it will be hacky !)
FormControl
is not injectable. Directives are injectable, but, you would have to deal with formControlName
,ngModel
,formControl
, etc and they wouldn't be accessible from the wrapping component but its children...
For your case you could try with @ContentChildren(FormControlName) theControl: any;
as there is no FormControlDirective
implied in your code, but you wouldn't be able to access the FormControl
anyway (property _control
is internal, soo it would be a hack)...
So you should stick to managing your errors from the component dealing with the FormGroup
.
BUT if you want to display a custom input (that won't display error message as is but will be able to show this input is in error state (the host element will get the ng-valid
, ng-invalid
classes, so it's just a matter of style), you can do this by implementing ControlValueAccessor
.
A bridge between a control and a native element.
A ControlValueAccessor abstracts the operations of writing a new value to a DOM element representing an input control.
It means directives/components implementing this interface can be used with ngModel
, formControl
,etc...
eg: <my-component [(ngModel)]="foo"></my-component>
it is not the exact reproduction of your problem, but this implementation solved the same kind of 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 just :
<field label="Title" formControlName="title"></field>
You can get ahold of the Form Control Name instance via:
@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);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With