Русский
Русский
English
Статистика
Реклама

Multiselect

Кастомные Emitterы и Subjectы в Angular инкапсулируем логику Toggle и MultiSelect

19.12.2020 18:18:04 | Автор: admin

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

Кейс 1: Переключалка (Toggle)

Часто в исходниках приходится видеть примерно такой код:

export class SampleComponent {@Output somethingSelected = new EventEmitter<boolean>()  ...  private _selected = false;  toggleSelected() {  this._selected = !this._selected;      this.somethingSelected.emit(this._selected);  }}

либо такой:

export class SampleComponent {@Output somethingSelected = new EventEmitter<boolean>()  ...  private _selected$ = new BehaviorSubject<boolean>(false);  toggleSelected() {  this._selected$.next(!this._selected$.value);      this.somethingSelected.emit(this._selected$.value);  }}

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

Попробуем унаследоваться от BehavoirSubject и добавить туда метод toggle()

export class ToggleSubject extends BehaviorSubject<boolean> {toogle() {    this.next(!this.value);    }}

Таким образом код компонента у нас приобретает вид:

export class SampleComponent {    @Output somethingSelected = new EventEmitter<boolean>()  ...  private _selected$ = new ToggleSubject(false);  toggleSelected() {      this._selected$.toggle();      this.somethingSelected.emit(this._selected$.value);  }}

уже получше, но кода стало меньше не намного. Попробуем вовсе избавиться от метода toggleSelected и приватного свойства _selected. Можно создать класс ToggleSwitcher и унаследовать его от EventEmitter

export class ToggleSwitcher extends EventEmitter<boolean> {get value(): boolean {    return this._value    }    constructor(private _value = false) {super();    }    toggle() {    this.emit(!this.value);    }    emit(v: boolean) {    this._value = v;        super.emit(v);    }}

теперь наш компонент приобретает такой вид:

export class SampleComponent {    @Output somethingSelected = new ToggleSwitcher()   ...}

в шаблоне для переключения можем использовать somethingSelected.toggle() для получения текущего значения somethingSelected.value для задания значения somethingSelected.emit(true / false). Если нужно значение по умолчанию true, можем его передать в конструктор ToggleSwitcher. Поскольку мы унаследовались от EventEmitter, проблем с эмитом событий также не будет.

@Output somethingSelected = new ToggleSwitcher(true)

Плюс такого решения очевиден: минимум бойлерплейта, все просто и локанично. Однако перфекционист может сказать, что тут нарушается SRP. Ведь EventEmitter у нас служит для эмита событий, а мы через наследование вешаем на него еще дополнительную логику по переключению. Чтож, есть еще один вариант. Можем не наследоваться от EventEmitter, а получать его из свитчера.

export class ToggleSwitcher extends BehaviorSubject<boolean> {eventEmitter = new EventEmitter<boolean>;        next(v: boolean) {    this.eventEmitter.emit(v);        super.next(v);    }        toggle() {    this.next(!this.value)    }}

Но тогда в компоненте будет на одну строчку больше кода, чем в предыдущем варианте

export class SampleComponent {somethingSwitcher = new ToggleSwitcher(false);    @Output somethingSelected = this.somethingSwitcher.eventEmitter;}

Кейс 2: множественный выбор

Также наиболее часто встечающийся кейс: на странице отобаржается список сущностей, должна быть возможность выбирать из списка нужные сущности, нужно показывать общее количество сущностей, количество выбранных сущностей, должна быть кнопка выбрать все и очистить выбор. В Output() нужно эмиттить массив выбранных сущностей.

Также должна быть возможность показывать в шаблоне через ngFor выбрана ли сущность или нет. Поэтому в *ngFor будем ложить не массив сущностей, а массив стейтов, содержащих сущность и сосотяние: выбран / не выбран

export class EntityCheckedState<T> {entity: T;    checked: boolean}export class EntityMultiSelector<T> extends BehaviorSubject<T[]> {private _list: EntityCheckedState<T>[];eventEmitter = new EventEmitter<T[]>();        get list(): EntityCheckedState<T>[] {    return this._list;    }set list(v: EntityCheckedState<T>[]) {     this._list = v;      this.next(this.list.filter(v => v.entity === entity);    }constructor(v: T[]; defaultChecked = false) {      super(defaultChecked? v || []);      this.eventEmitter.emit(defaultChecked? v || []);      this._list = v.map(entity => ({entity, checked: defaultChecked})    }                               setCheckedForEntity(entity: T, checked: boolean) {         this.list = this.list.map(v => (v.entity === entity ? { ...v, checked });    }setCheckedForAll(checked: boolean) {      this.list = this.list.map(v => ({...v, checked}));    }next(v: T[]) {      this.eventEmitter.emit(v);suer.next(v);    }}

юзаем в компоненте:

export class SampleComponent {@Input set data(v: SampleDto) {    this.multiSelector = new EntityMultiSelector<SampleDto>;        this.selectedSamples = this.multiSelector.eventEmiter;    }    multiSelector: EntityMultiSelector<SampleDto>;    @Output selectedSamples: EventEmitter<SampleDto[]>}

Как это будет выглядеть в шаблоне:

<app-sample-entity *ngFor = "let state of multiSelector.list"                                        [data] = "state.entity"                    [checked] = "state.checked"                    (checked) = "multiSelector.setCheckedForEntity(state.entity, $event)" ></app-sample-entity> Всего: {{multiSelector.list.length}} Выбрано: {{multiSelector.value.lenght}} <button (click) = "multiSelector.setSelectedForAll(false)">Очистить</button>                    
Подробнее..
Категории: Angular , Toggle , Multiselect

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru