У меня есть следующие компоненты:
@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> {
}
и простая директива, в которой я хотел бы изменить «предупреждения» свойства @Input моих компонентов.
@Directive({
selector: '[validation]',
})
export class ValidationDirective {
constructor(private readonly control: NgControl) {
}
}
Я попытался внедрить AbstractComponent в конструктор:
constructor(
private readonly control: NgControl,
@Host() readonly component: AbstractControlComponent<any>) {
}
ngOnInit(): void {
this.component.warnings = ['test1', 'test2'];
}
Но это дает мне ошибку:
Error: NG0201: No provider for _AbstractControlComponent found in NodeInjector
И я не хочу вводить конкретные компоненты, поскольку моя директива является общей.
Знаешь, как мне этого добиться?
Можете ли вы предоставить ссылку на stackblitz вашей установки?
Вы должны предоставить его, чтобы компонент внутри мог найти AbstractControlComponent. Чтобы компонент был доступен в инжекторе. И это должно быть доступно в директиве.
@Component({
selector: 'test-input',
templateUrl: './input.component.html',
styleUrls: ['./input.component.scss'],
providers: [
// provide the AbstractControlComponent
{
provide: AbstractControlComponent,
useExisting: InputComponent
}
]
})
export class InputComponent extends AbstractControlComponent<string> {
Вы можете использовать собственный токен внедрения вместе с декораторами @Host
и @Optional
.
Сначала вам нужно создать токен инъекции:
import { InjectionToken } from '@angular/core';
export const ABSTRACT_CONTROL_COMPONENT = new InjectionToken<AbstractControlComponent<any>>('AbstractControlComponent');
Затем предоставьте его своим пользовательским компонентам ввода:
@Component({
selector: 'test-input',
templateUrl: './input.component.html',
styleUrls: ['./input.component.scss'],
providers: [
{ provide: ABSTRACT_CONTROL_COMPONENT, useExisting: InputComponent }
]
})
export class InputComponent extends AbstractControlComponent<string> { }
@Component({
selector: 'test-datepicker',
templateUrl: './datepicker.component.html',
styleUrls: ['./datepicker.component.scss'],
providers: [
{ provide: ABSTRACT_CONTROL_COMPONENT, useExisting: DatepickerComponent }
]
})
export class DatepickerComponent extends AbstractControlComponent<Date> { }
После этого вы можете добавить токен в свою директиву:
import { Directive, Host, Optional, Inject } from '@angular/core';
import { NgControl } from '@angular/forms';
import { ABSTRACT_CONTROL_COMPONENT } from './path-to-token-file';
import { AbstractControlComponent } from './path-to-abstract-component-file';
@Directive({
selector: '[validation]',
})
export class ValidationDirective {
constructor(
private readonly control: NgControl,
@Host() @Optional() @Inject(ABSTRACT_CONTROL_COMPONENT) readonly component: AbstractControlComponent<any>
) { }
ngOnInit(): void {
if (this.component) {
this.component.warnings = ['test1', 'test2'];
}
}
}
Проблема в том, что у самого компонента действительно нет поставщика AbstractControlComponent
. Компонент только наследует AbstractControlComponent
, но не предоставляет его.
Чтобы предоставить конкретный компонент в качестве AbstractControlComponent
, вам необходимо объявить это в поставщиках:
@Component({
selector: 'test-datepicker',
templateUrl: './datepicker.component.html',
styleUrls: ['./datepicker.component.scss'],
providers: [
{
provide: AbstractControlComponent,
useExisting: forwardRef(() => DatepickerComponent)
}
]
})
export class DatepickerComponent extends AbstractControlComponent<Date> {}
Таким образом, вы сможете внедрить контроллер в ValidationDirective
.
С архитектурной точки зрения я бы предпочел сделать наоборот. Вместо записи во входные данные я бы внедрил класс, который предоставляет сообщения в AbstractControlComponent, и считывал бы из него данные.
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"]
}
Мне нравится ваш второй вариант, но он выдает следующую ошибку: NullInjectorError: NullInjectorError: No provider for WarningSource!
. Первый вариант работает хорошо, спасибо!
Второй вариант должен быть необязательным, если не все AbstractControls имеют директиву Validation. Я обновил свой код. Буду признателен за отзыв, если он решит проблему.
warningSource
не вводится (ноль) в AbstractControlComponent
При неавтономном подходе вы все объявляете прямо в своих модулях?