-
-
Save cyrillbrito/f387212029bcc97287088297492c54d8 to your computer and use it in GitHub Desktop.
| @Directive({ | |
| standalone: true, | |
| providers: [ | |
| { | |
| provide: NG_VALUE_ACCESSOR, | |
| multi: true, | |
| useExisting: HostControlDirective, | |
| }, | |
| ], | |
| }) | |
| export class HostControlDirective implements ControlValueAccessor { | |
| control!: FormControl; | |
| private injector = inject(Injector); | |
| private subscription?: Subscription; | |
| ngOnInit(): void { | |
| const ngControl = this.injector.get(NgControl, null, { self: true, optional: true }); | |
| if (ngControl instanceof FormControlName) { | |
| const group = this.injector.get(ControlContainer).control as UntypedFormGroup; | |
| this.control = group.controls[ngControl.name!] as FormControl; | |
| return; | |
| } | |
| if (ngControl instanceof FormControlDirective) { | |
| this.control = ngControl.control; | |
| return; | |
| } | |
| if (ngControl instanceof NgModel) { | |
| this.subscription = ngControl.control.valueChanges.subscribe(newValue => { | |
| // The viewToModelUpdate updates the directive and triggers the ngModelChange. | |
| // So we want to called it when the value changes except when it comes from the parent (ngModel input). | |
| // The `if` checks if the newValue is different from the value on the ngModel input or from the current value. | |
| if (ngControl.model !== newValue || ngControl.viewModel !== newValue) { | |
| ngControl.viewToModelUpdate(newValue); | |
| } | |
| }); | |
| this.control = ngControl.control; | |
| return; | |
| } | |
| // Fallback | |
| this.control = new FormControl(); | |
| } | |
| writeValue(): void { } | |
| registerOnChange(): void { } | |
| registerOnTouched(): void { } | |
| ngOnDestroy(): void { | |
| this.subscription?.unsubscribe(); | |
| } | |
| } | |
| // Usage example | |
| @Component({ | |
| selector: 'app-custom-input', | |
| template: `<input [formControl]="hcd.control" />`, | |
| standalone: true, | |
| imports: [ReactiveFormsModule], | |
| hostDirectives: [HostControlDirective], | |
| }) | |
| export class CustomInputComponent { | |
| hcd = inject(HostControlDirective); | |
| } |
@jacobfederer Did not really understand your question. What do you mean by "custom version of NgControl" ?
There is an example of how to use in the snippet.
Yes, I just wanted to write a unit test for my component and wondered on how to inject an instance of NgControl in this case. But I found a solution for it. This seems to work:
let testFormControl: FormControl
beforeEach(waitForAsync(() => {
testFormControl = new FormControl(null);
const formControlDirective = new FormControlDirective([], [], [
// At least one control value accessor is required
{writeValue: () => null, registerOnChange: () => null, registerOnTouched: () => null}
], null, null);
formControlDirective.form = testFormControl
formControlDirective.name = "test"
TestBed.configureTestingModule({
imports: [ParentFormControlDirective, TextAreaComponent, NoopAnimationsModule],
}).overrideComponent(NextTextAreaComponent, {
// A regular provider does not work with the injector self-flag, you need to override it like this
set: {
providers: [
{
provide: NgControl,
useValue: formControlDirective
}
]
}
})
.compileComponents();
}));
Hi @cyrillbrito when I change the reference of a form group the formcontrols dosent update the reference
Do you have the working example? With an example usage? Thanks in advance!
of course @vivekraj-kr as you can see in the image when I use the control with Control, it always binds me to the reference of the first form:
Thanks for this. I needed to use it both with formControlName and ngModel...
Lost 1 hour wondering why some logic didn't work, before realizing the ngModel wasn't working with the original hack...


@cyrillbrito I like your solution! But how do you test it? I tried to provide a custom version of NgControl, but the self option of the
Injector.getseems to prevent this. In my dependent component I'm unable to inject a working version of NgControl.