-
-
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); | |
} |
@cyrillbrito I get and error with your example:
@pookdeveloper Fixed the depredated flags, thanks. Maybe use are using NoopValueAccessorDirective
together with my HostControlDirective
and since both provide NG_VALUE_ACCESSOR
it is causing problems. With my solution you don't need the NoopValueAccessorDirective
since it is already incorporated
@cyrillbrito your HostControlDirective is my NoopValueAccessorDirective i only use this name , but i hace the same code
The problem is that I have move the code in NgOninit to constructor.
I had some problems with array values, the code above caused the changes to be triggered twice, so I changed the condition to this:
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);
}
});
So that only when both models are different it triggers the update. Now it seems to work
@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.get
seems to prevent this. In my dependent component I'm unable to inject a working version of NgControl.
@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:
@cyrillbrito nice work, only .. Inject flags are deprecated:
const ngControl = this.injector.get(NgControl, null, InjectFlags.Self + InjectFlags.Optional);
Now:
const ngControl = this.injector.get(NgControl, null, {self: true, optional: true});