Angular Как изменить стиль связанных компонентов при наведении на элемент, который не является прямым соседом

Вопрос или проблема

Я новичок в Angular и не уверен, что использование rxjs — лучший способ достичь этого. Представьте, что у вас есть некоторый текст, каждое слово — это компонент, и, к сожалению, эти компоненты не все являются прямыми братьями и сестрами. Слово или группа слов могут иметь аннотационный компонент, например, число над текстом, так же как в книгах. При наведении на аннотацию мне нужно изменить стиль связанного с ней слова, кроме самого слова, конечно. Также, если я навожу на слова, аннотация должна изменить свой стиль, снова, и сами слова тоже.

Я попробовал использовать общий сервис и subjects и т.д., но в итоге все экземпляры слова и аннотационного компонента прослушивают все вызовы subject.next, и каждому экземпляру компонента нужно фильтровать входящее событие по какому-то идентификатору. Когда компонентов много, это начинает тормозить.

Стоит ли использовать входные параметры, чтобы сделать это через управление состоянием в родительском компоненте?

Извините, что не могу предоставить пример кода. Это немного упрощено, в word.component:

onHover() {
   this.hoverService.hoveredWord$.next(this.word.id);
}

В annotation.component:

shouldHighlight$ = combineLatest([
    this.onAnnotationHover$, // это вызывается внутри этого компонента с помощью this.onAnnotationHover.next()
    this.hoverService.hoveredWord$,
]).pipe(
   map(([hover, word]) => hover || this.annotationData.includes(word));

И наоборот, для наведения на аннотации, а затем наблюдения в компоненте слова. Я думаю, что я спрашиваю не конкретный код, а есть ли лучший способ сделать это.

Ответ или решение

Изменение стилей связанных компонентов при наведении курсора в Angular

Вопрос, который вы поднимаете, довольно распространён в разработке с использованием Angular, особенно среди новичков. В вашей ситуации необходимо управлять стилями компонентов, которые не являются непосредственными соседями, при наведении курсора на определённый элемент, например, аннотацию для слова. Рассмотрим, как это можно реализовать.

Основные подходы к решению задачи

  1. Использование Общего Сервиса:
    Хотя вы уже пробовали использовать общий сервис с RxJS, у такого подхода есть свои недостатки. Как вы заметили, при увеличении количества компонентов всё становится менее производительным, поскольку каждое изменение требует от всех подписчиков обработки событий.

  2. Управление состоянием через родительский компонент:
    Многие опытные разработчики предпочитают подход с родительским компонентом для управления состоянием. Это позволит вам централизовать логику наведения, избегая глобального “флуда” событий. СостояниеHover может передаваться вниз по иерархии компонентов через @Input() и @Output().

  3. Использование Angular CDK:
    Angular Component Development Kit (CDK) предоставляет средств для управления взаимодействиями между компонентами. Используйте его для реализации более продвинутых паттернов взаимодействия.

Пример реализации через родительский компонент

Возьмем за основу структуру вашего компонента. Создадим родительский компонент, который будет контролировать состояние наведения:

@Component({
  selector: 'app-parent',
  template: `
    <div (mouseenter)="onHover(null)" (mouseleave)="onHover(null)">
      <app-word *ngFor="let word of words" [word]="word" (hover)="onHover(word.id)"></app-word>
      <app-annotation [annotations]="annotations" (hover)="onHover(null)"></app-annotation>
    </div>
  `
})
export class ParentComponent {
  words = [...];  // массив слов
  annotations = [...]; // массив аннотаций
  hoveredWordId: number | null = null;

  onHover(wordId: number | null) {
    this.hoveredWordId = wordId;
  }
}

Компонент слова

@Component({
  selector: 'app-word',
  template: `
    <span [ngClass]="{'highlighted': isHovered()}" (mouseenter)="hover.emit()" (mouseleave)="hover.emit()">
      {{ word }}
    </span>
  `
})
export class WordComponent {
  @Input() word: string;
  @Output() hover = new EventEmitter<void>();

  isHovered(): boolean {
    return true; // Ваша логика определения стиля
  }
}

Компонент аннотации

@Component({
  selector: 'app-annotation',
  template: `
    <div (mouseenter)="onHover()" (mouseleave)="onHover()">
      <span [ngClass]="{'highlighted': isHovered()}">Аннотация</span>
    </div>
  `
})
export class AnnotationComponent {
  @Input() annotations: string[];

  isHovered(): boolean {
    return true; // Ваша логика для аннотаций
  }
}

Преимущества такого подхода

  • Производительность: Обработка состояния осуществляется в родительском компоненте, что снижает количество необходимых подписок.
  • Простота: Логика наведения складывается в одном месте, что упрощает отладку и сопровождение кода.
  • Масштабируемость: Этот метод легко адаптировать для большего количества компонентов, так как вы используете один механизм состояния.

Заключение

Ваше желание найти более эффективное решение вполне обосновано. Описанный выше подход с использованием родительского компонента как менеджера состояния может значительно улучшить производительность и удобство разработки. Настоятельно рекомендую вам протестировать этот метод и адаптировать его под свои нужды. Не забывайте, что с увеличением сложности приложения может потребоваться более сложное управление состоянием, такое как NgRx или Akita.

Оцените материал
Добавить комментарий

Капча загружается...