I have the following components :
@Directive()
export abstract class AbstractControlComponent<T> implements ControlValueAccessor {
...
@Input() warnings: string[] = [];
...
}
@Component({
selector: 'test-input',
templateUrl: './input.component.html',
styleUrls: ['./input.component.scss']
})
export class InputComponent extends AbstractControlComponent<string> {
}
@Component({
selector: 'test-datepicker',
templateUrl: './datepicker.component.html',
styleUrls: ['./datepicker.component.scss']
})
export class DatepickerComponent extends AbstractControlComponent<Date> {
}
and a simple Directive where I would like to change the @Input property 'warnings' of my components.
@Directive({
selector: '[validation]',
})
export class ValidationDirective {
constructor(private readonly control: NgControl) {
}
}
I tried to inject the AbstractComponent in the constructor :
constructor(
private readonly control: NgControl,
@Host() readonly component: AbstractControlComponent<any>) {
}
ngOnInit(): void {
this.component.warnings = ['test1', 'test2'];
}
But it gives me an error :
Error: NG0201: No provider for _AbstractControlComponent found in NodeInjector
And I don't want to inject concrete components as my directive is generic.
Do you know how I can achieve that ?
The problem is that there really is no provider for AbstractControlComponent
by the component itself. The component only inherits AbstractControlComponent
but does not provide it.
In order to provide the concrete component as the AbstractControlComponent
, you need to declare so in the providers:
@Component({
selector: 'test-datepicker',
templateUrl: './datepicker.component.html',
styleUrls: ['./datepicker.component.scss'],
providers: [
{
provide: AbstractControlComponent,
useExisting: forwardRef(() => DatepickerComponent)
}
]
})
export class DatepickerComponent extends AbstractControlComponent<Date> {}
That way you'll be able inject the controller into the ValidationDirective
.
From architectural point of view, I'd rather do things the other way around. Instead of writing into an input, I'd inject a class that provides the messasges into the AbstractControlComponent and read data from it.
export abstract class WarningSource {
abstract get warnings(): string[]
}
@Directive()
export abstract class AbstractControlComponent<T> implements ControlValueAccessor {
...
private warningSource = inject(WarningSource, { optional: true })
get warnings() {
return this.warningSource?.warnings ?? [];
}
...
}
@Directive({
selector: '[validation]',
providers: [
{
provide: WarningSource,
useExisting: forwardRef(() => ValidationDirective)
}
]
})
export class ValidationDirective extends WarningSource {
warnings = ["This is a warning"]
}