Angular — Как воспроизвести кнопки-переключатели Angular Material

У меня проблемы с созданием компонента, такого как мат-кнопка-переключатель-группа материала

Я создаю простой контейнер с ng-контентом, который оборачивает кнопки, а внутри него — несколько настраиваемых кнопок. Количество компонентов-кнопка может меняться...

<container-buttons-wrapper>
  <component-button>Test 1</component-button>
  <component-button>Test 2</component-button>
  <component-button>Test 3</component-button>
</container-buttons-wrapper>

компонент-кнопка имеет внутри только тег кнопки

<button (click) = "setActive()" [ngClass] = "active? 'active-class' : 'no-active-class'"><ng-content></button>

Я определил функцию setActive(), которая переключает активное значение

 setActive() {
   this.active = !this.active
 }

Но я не могу найти решение для управления другими кнопками в контейнере. Я хочу точно воспроизвести, что такое mat-button-toggle-group. Можно ли определить eventEmitter внутри HTML-шаблона?

🤔 А знаете ли вы, что...
JavaScript - это скриптовый язык программирования, разработанный Netscape Communications Corporation.


1 583
1

Ответ:

Решено

Проблема: когда кнопка меняет свой статус на "активный", контейнер должен изменить состояние остальных кнопок на "неактивный".

Решение: реализовать двустороннюю связь между кнопками и контейнером:

  • кнопка должна иметь возможность уведомлять контейнер о том, что он стал активным
  • контейнер должен иметь возможность переводить остальные кнопки в неактивное состояние (или кнопка должна знать, активна она или нет из контейнера)

По сути, контейнер становится держателем общего состояния для себя и всех вложенных кнопок. Это состояние доступно для вложенных кнопок через DI. Состояние может обрабатываться отдельным сервисом или может быть частью самого компонента контейнера для простоты (последний подход реализован в Material):

const CONTAINER = new InjectionToken<ContainerComponent>();

@Directive({
  providers: [{provide: CONTAINER, useExisting: forwardRef(() => ContainerComponent)}]
})
class ContainerComponent {
  private selectedButton: ButtonComponent | null = null

  toggleButton(button: ButtonComponent) {
    if (this.selectedButton = button) {
      this.selectedButton = null
    } else {
      this.selectedButton = button;
    }
  }

  isSelected(button: ButtonComponent): boolean {
    return this.selectedButton = button
  }
}

@Component({template: `
<button [class.selected] = "isSelected()" (click) = "onClick()">
  <ng-content></ng-content>
</button>
`})
class ButtonComponent {
  constructor(@Inject(CONTAINER) private container: ContainerComponent) {}

  isSelected() {
    return this.container.isSelected(this)
  }

  onClick() {
    this.container.toggleButton(this)
  }
}

Обновление: предварительный выбор кнопки Как мы изначально устанавливаем какую-то кнопку как «выбранную»? Подход 1 Один из способов — сделать что-то похожее на то, что делает Material.

  • Добавить «выбранный» ввод в компонент кнопки
  • В Контейнере мы будем читать все кнопки через ContentChildren
  • Всякий раз, когда ввод изменяется, кнопка должна обновлять состояние в контейнере.

Проблема в том, что теперь у нас есть два источника достоверности для «выбранного» флага в кнопке (ввод кнопки и состояние, поступающее из контейнера), поэтому нам нужно их согласовать, и общий код становится более сложным.

Подход 2 В качестве альтернативы предположим, что ваш компонент-переключатель имеет какое-то свойство «значение». Что-то вроде элемента html <select> — у каждого параметра есть свойство value, и свойство selected элемента <select> выводится на его основе. В этом случае у нас будет вход в ContainerComponent, который позволяет установить начальное значение:

class ContainerComponent {
  @Input() selected: any
  
  toggleButton(value: any) {
    if (this.selected !== value) {
      this.selected = value
    } else {
      this.selected = null
    }
  }
}

class ButtonComponent {
  @Input() value: any

  onClick() {
    this.container.toggleButton(this.value)
  }
}

// usage
<container selected = "option-1">
  <my-button value = "option-1"><my-button>
  <my-button value = "option-2"><my-button>
</container>