Reputation: 815
I'm trying to inverse the way forms controls are registering themselves onto a FormGroup
, so that instead of having to
@Component({..., template: `<input [formControl]="myControl"`}
...
Or
@Component({..., template: `<input [formControName]="myControlName"`}
...
I could
@Component({..., template: `<input myFormControl`}
...
And have my directive create and add the FormControl
for me.
It is best explained with this Plunker.
What doesn't seem to work is the binding the view to the form model, as you can see, changing the the input does not change the form model value.
Debugging it shows that there are no valueAccessor
injected to the constructor (unlike when using the base FormControlDirective
class directly).
If you're wondering, my end goal would be that I would have a parent customized group component that would @ViewChildren(MyFormDirective)
, and dynamically add them all to it's created form.
Upvotes: 3
Views: 2706
Reputation: 2389
I ran into the same issue. Odd that there aren't more Stackoverflow posts about this. The above answer did not work for me, but this is how I solved it in the case that there are more out there.
@Directive({
selector: '[hybridFormControl]'
})
class HybridFormControl Directive extends FormControlName implements ControlValueAccessor, OnChanges {
@Input('hybridFormControl') name: string;
onChange;
onTouched;
constructor(
@Optional() protected formGroupDirective: FormGroupDirective,
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[],
private fb: FormBuilder,
private renderer: Renderer2,
private element: ElementRef
) {
super(formGroupDirective, [], [], valueAccessors, null);
this.valueAccessor = this;
}
ngOnChanges(changes: SimpleChanges): void {
if (!this._registered) {
// dynamically create the form control model on the form group model.
this.formGroup = this.formGroupDirective.form;
this.formGroup.registerControl(name, this.fb.control(''));
this._registered = true;
}
// IMPORTANT - this ties your extended form control to the form control
// model on the form group model that we just created above. Take a look
// at Angular github source code.
super.ngOnChanges(changes);
}
@HostListener('input', ['$event.target.value'])
@HostListener('change', ['$event.target.value'])
onInput(value): void {
this.onChange(modelValue);
}
writeValue(value): void {
const element = this.element.nativeElement;
this.renderer.setProperty(element, 'value', value);
}
registerOnChange(fn): void {
this.onChange = fn;
}
registerOnTouched(fn): void {
this.onTouched = fn;
}
}
And this hybrid component could be used like:
@Component({
selector: 'app',
template: `
<form formGroup=[formGroup]>
<input type="text" hybridFormControl="myName">
</form>
`
class AppComponent {
formGroup: FormGroup
constructor(fb: FormBuilder) {
this.form = this.fb.group({});
}
}
Upvotes: 0
Reputation: 1920
You are almost there. There is one more trick though. There isn't DefaultValueAccessor
for that input element, thus constructor arguments are populate with null
value.
The formControl
\ formControlName
selectors appear in one more place - the value accessor. In order your directive to work you should implement all default value accessors for the hybridFormControl
directive ( following the pattern for the built-in directives).
P.S I believe the provider of your directive should be corrected to
providers: [{
provide: NgControl, //<-- NgControl is the key
useExisting: forwardRef(() => HybridFormControlDirective)
}]
Upvotes: 3