How to dynamically add NG_VALUE_ACCESSOR component to reactive form?
You can try to extend NgControl
. Here is simple implementation. But it might be more complex.
dynamic-panel.directive.ts
@Directive({
selector: '[dynamic-panel]'
})
export class DynamicPanelDirective extends NgControl implements OnInit {
name: string;
component: ComponentRef<any>;
@Output('ngModelChange') update = new EventEmitter();
_control: FormControl;
constructor(
@Optional() @Host() @SkipSelf() private parent: ControlContainer,
private resolver: ComponentFactoryResolver,
private container: ViewContainerRef) {
super();
}
ngOnInit() {
let component = this.resolver.resolveComponentFactory<GeneralPanelComponent>(GeneralPanelComponent);
this.name = 'general';
this.component = this.container.createComponent(component);
this.valueAccessor = this.component.instance;
this._control = this.formDirective.addControl(this);
}
get path(): string[] {
return [...this.parent.path !, this.name];
}
get formDirective(): any { return this.parent ? this.parent.formDirective : null; }
get control(): FormControl { return this._control; }
get validator(): ValidatorFn|null { return null; }
get asyncValidator(): AsyncValidatorFn { return null; }
viewToModelUpdate(newValue: any): void {
this.update.emit(newValue);
}
ngOnDestroy(): void {
if (this.formDirective) {
this.formDirective.removeControl(this);
}
if(this.component) {
this.component.destroy();
}
}
}
Modified Plunker
So
How to dynamically add NG_VALUE_ACCESSOR component to reactive form?
this.valueAccessor = this.component.instance;
in my case
If you want to use validators then see this Plunker
The current accepted answer works for the exact scenario in the original post, but a slightly different scenario led me to this post. Thanks to @yurzui, I was able to find a solution based on his answer.
My own solution allows for full integration (including ngModel, reactive forms and validators) into the Angular form ecosystem using the usual declarative bindings in the template. So I'm posting it here in case anybody else will come here looking for this.
You can check it out on StackBlitz.
import {
Component,
ComponentFactoryResolver,
forwardRef,
Host,
Injector,
SkipSelf,
ViewContainerRef,
} from '@angular/core';
import { ControlContainer, NgControl, NG_VALUE_ACCESSOR } from '@angular/forms';
import { CustomInputComponent } from './custom-input.component';
@Component({
selector: 'app-form-control-outlet',
template: ``,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => FormControlOutletComponent),
multi: true,
},
],
})
export class FormControlOutletComponent {
constructor(
public injector: Injector,
private componentFactoryResolver: ComponentFactoryResolver,
private viewContainerRef: ViewContainerRef,
) {}
public ngOnInit(): void {
const ngControl = this.injector.get(NgControl);
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(
/**
* Retrieve this component in whatever way you would like,
* it could be based on an @Input or from a service etc...
*/
CustomInputComponent,
);
const componentRef = this.viewContainerRef.createComponent(
componentFactory,
);
ngControl.valueAccessor = componentRef.instance;
}
}