Используйте директиву для изменения атрибута @Input компонента

У меня есть следующие компоненты:

@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 И я не хочу вводить конкретные компоненты, поскольку моя директива является общей.

Знаешь, как мне этого добиться?

При неавтономном подходе вы все объявляете прямо в своих модулях?

Marlon 30.07.2024 12:40

Можете ли вы предоставить ссылку на stackblitz вашей установки?

Rachid O 30.07.2024 14:33
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Angular и React для вашего проекта веб-разработки?
Angular и React для вашего проекта веб-разработки?
Когда дело доходит до веб-разработки, выбор правильного front-end фреймворка имеет решающее значение. Angular и React - два самых популярных...
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Мы провели Twitter Space, обсудив несколько проблем, связанных с последними дополнениями в Angular. Также прошла Angular Tiny Conf с 25 докладами.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
Мое недавнее углубление в Angular
Мое недавнее углубление в Angular
Недавно я провел некоторое время, изучая фреймворк Angular, и я хотел поделиться своим опытом со всеми вами. Как человек, который любит глубоко...
Освоение Observables и Subjects в Rxjs:
Освоение Observables и Subjects в Rxjs:
Давайте начнем с основ и постепенно перейдем к более продвинутым концепциям в RxJS в Angular
1
2
63
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Вы должны предоставить его, чтобы компонент внутри мог найти 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!. Первый вариант работает хорошо, спасибо!

isy 30.07.2024 14:41

Второй вариант должен быть необязательным, если не все AbstractControls имеют директиву Validation. Я обновил свой код. Буду признателен за отзыв, если он решит проблему.

kvetis 30.07.2024 15:18
warningSource не вводится (ноль) в AbstractControlComponent
isy 30.07.2024 15:54

Другие вопросы по теме