angular
February 27

Angular: как использовать директивы внутри <select>

Предположим, что тестовое приложение содержит различные селекты, которые часто используются в разных частях приложения.

Один из подходов к управлению этими компонентами заключается в том, чтобы индивидуально запрашивать опции и передавать их в компонент выбора при каждом использовании, однако этот подход не соответствует принципу DRY (Don't Repeat Yourself).

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

@Component({
  selector: 'app-users-select',
  standalone: true,
  imports: [SelectComponent, CommonModule],
  template: `
    <app-select
      placeholder="Select user"
      [options]="users$ | async"
      [allowClearSelected]="allowClearSelected"
    ></app-select>
  `
})
export class UsersSelectComponent implements ControlValueAccessor {
  @Input() allowClearSelected: boolean;

  users$ = inject(UsersService).getUsers().pipe(
    map((users) => {
      return users.map((user) => {
        return {
          id: user.id,
          label: user.label,
        };
      });
    })
  )

  // ... ControlValueAccessor...
}

Хотя этот подход будет работать, он не является оптимальным. Во-первых, каждому компоненту потребуется новый управляющий доступ к значению. Более того, чтобы позволить потребителям устанавливать @Input и @Output, то компонент выбора должен проксировать свой API к обернутому компоненту.

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

import { Directive, inject } from '@angular/core';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';

@UntilDestroy()
@Directive({
  selector: '[usersSelectOptions]',
  standalone: true,
})
export class UsersSelectOptionsDirective {
  private select = inject(SelectComponent);
  private users$ = inject(UserService).getUsers();

  ngOnInit() {
    this.select.placeholder = 'Select user';

    this.users$.pipe(untilDestroyed(this)).subscribe((users) => {
       this.select.options = users.map((user) => {
         return {
           id: user.id,
           label: user.name,
         };
       });
    });
  }
}

В итоге используя DI получаем ссылку на компонент SelectComponent. В ngOnInit свойство select.placeholder устанавливается в «Выберите пользователя».

Мы получаем пользователей и устанавливаем опции выбора. Теперь мы можем использовать его в нашем селекте:

<app-select
  usersSelectOptions <=====
  formControlName="userId"
  [allowClearSelected]="false"
></app-select>