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;
}
}