[angular] CDK之collections 集合

362 阅读3分钟

集合组件工具,里面提供一些公用的服务类和工具类,主要用于数据收集及类别区分,如多选、单选

服务类 UniqueSelectionDispatcher

import {Injectable, OnDestroy} from '@angular/core';

// 唯一选择分配监听器
export type UniqueSelectionDispatcherListener = (id: string, name: string) => void;

/**
 * 和发布订阅一样,这里不同在提供一个全局单例服务,定义了监听回调格式,没有提供单个注销方法,只能通过listen监听返回函数取消自身监听
 */
@Injectable({providedIn: 'root'})
export class UniqueSelectionDispatcher implements OnDestroy {
  private _listeners: UniqueSelectionDispatcherListener[] = [];

  /**
   * 通知监听更改.
   */
  notify(id: string, name: string) {
    for (let listener of this._listeners) {
      listener(id, name);
    }
  }

  /**
   * 监听项目选择的未来更改.
   * 返回一个函数 用于注销监听器
   */
  listen(listener: UniqueSelectionDispatcherListener): () => void {
    this._listeners.push(listener);
    return () => {
      this._listeners = this._listeners.filter((registered: UniqueSelectionDispatcherListener) => {
        return listener !== registered;
      });
    };
  }

  ngOnDestroy() {
    this._listeners = [];
  }
}

没有数据保存要求、只监听数据变化可以使用此服务,通过id和name来区分项目

选择模型 SelectionModel

import { Subject } from 'rxjs';

/**
 *
 * 这个类可以快速从一个列表中选择一个和多个. 选择模型
 */
export class SelectionModel<T> {
  /** 当前选择了的值. */
  private _selection = new Set<T>();

  /** 跟踪更改事件并没有发出的反选选项. */
  private _deselectedToEmit: T[] = [];

  /** 跟踪更改事件并没有发出的已选选项. */
  private _selectedToEmit: T[] = [];

  /** 缓存已选项目的值. */
  private _selected: T[] | null;

  /** 获取已选的值. */
  get selected(): T[] {
    if (!this._selected) {
      this._selected = Array.from(this._selection.values());
    }

    return this._selected;
  }

  /** 值更改时触发的事件. */
  readonly changed = new Subject<SelectionChange<T>>();

  constructor(
    private _multiple = false, // 是否为多选
    initiallySelectedValues?: T[], // 初始的已选中的值
    private _emitChanges = true, // 是否触发更改事件
    public compareWith?: (o1: T, o2: T) => boolean, // 自定义两值比较方法
  ) {
    if (initiallySelectedValues && initiallySelectedValues.length) {
      if (_multiple) {
        initiallySelectedValues.forEach(value => this._markSelected(value));
      } else {
        this._markSelected(initiallySelectedValues[0]);
      }
      // 清除数组以避免触发预选值的更改事件.
      this._selectedToEmit.length = 0;
    }
  }

  /**
   * 选择一个值或一组值
   * @return 表示数据是否有更改
   */
  select(...values: T[]): boolean | void {
    this._verifyValueAssignment(values);
    values.forEach(value => this._markSelected(value));
    const changed = this._hasQueuedChanges();
    this._emitChangeEvent();
    return changed;
  }

  /**
   * 没有被选择的一个值或一组值.
   * @param values 没有选中的一组值
   * @return 表示数据是否有更改
   */
  deselect(...values: T[]): boolean | void {
    this._verifyValueAssignment(values);
    values.forEach(value => this._unmarkSelected(value));
    const changed = this._hasQueuedChanges();
    this._emitChangeEvent();
    return changed;
  }

  /**
   * 设置一组选中了的值
   */
  setSelection(...values: T[]): boolean | void {
    this._verifyValueAssignment(values);
    const oldValues = this.selected;
    const newSelectedSet = new Set(values);
    values.forEach(value => this._markSelected(value));
    oldValues.filter(value => !newSelectedSet.has(value)).forEach(value => this._unmarkSelected(value));
    const changed = this._hasQueuedChanges();
    this._emitChangeEvent();
    return changed;
  }

  /**
   * 在选中和取消选中之间切换值
   * @return 选择是否因此调用而改变
   */
  toggle(value: T): boolean | void {
    return this.isSelected(value) ? this.deselect(value) : this.select(value);
  }

  /**
   * 清除所有的选择数据
   * @param flushEvent 是否刷新事件中的更改
   * @return 是否因此调用而改变数据状态
   */
  clear(flushEvent = true): boolean | void {
    this._unmarkAll();
    const changed = this._hasQueuedChanges();
    if (flushEvent) {
      this._emitChangeEvent();
    }
    return changed;
  }

  /**
   * 确定一个值是否被选中.
   */
  isSelected(value: T): boolean {
    if (this.compareWith) {
      for (const otherValue of this._selection) {
        if (this.compareWith(otherValue, value)) {
          return true;
        }
      }
      return false;
    }
    return this._selection.has(value);
  }

  /**
   * 确认这个类是否有数据.
   */
  isEmpty(): boolean {
    return this._selection.size === 0;
  }

  /**
   * 确认这个类是否有数据..
   */
  hasValue(): boolean {
    return !this.isEmpty();
  }

  /**
   * 根据predicate函数对所选值进行排序.
   */
  sort(predicate?: (a: T, b: T) => number): void {
    if (this._multiple && this.selected) {
      this._selected!.sort(predicate);
    }
  }

  /**
   * 获取是否为多选.
   */
  isMultipleSelection() {
    return this._multiple;
  }

  /** 触发一个更改事件,并清除派发选中和取消选中值的记录. */
  private _emitChangeEvent() {
    // Clear the selected values so they can be re-cached.
    this._selected = null;

    if (this._selectedToEmit.length || this._deselectedToEmit.length) {
      this.changed.next({
        source: this,
        added: this._selectedToEmit,
        removed: this._deselectedToEmit,
      });

      this._deselectedToEmit = [];
      this._selectedToEmit = [];
    }
  }

  /** 标记值为选中. */
  private _markSelected(value: T) {
    if (!this.isSelected(value)) {
      if (!this._multiple) {
        this._unmarkAll();
      }

      if (!this.isSelected(value)) {
        this._selection.add(value);
      }

      if (this._emitChanges) {
        this._selectedToEmit.push(value);
      }
    }
  }

  /** 标记一值为未选中. */
  private _unmarkSelected(value: T) {
    if (this.isSelected(value)) {
      this._selection.delete(value);

      if (this._emitChanges) {
        this._deselectedToEmit.push(value);
      }
    }
  }

  /** 清除所有的所选值. */
  private _unmarkAll() {
    if (!this.isEmpty()) {
      this._selection.forEach(value => this._unmarkSelected(value));
    }
  }

  /**
   * 验证赋值是否正确,验证为多选还是单选,单选的选中值只能是一个
   */
  private _verifyValueAssignment(values: T[]) {
    if (values.length > 1 && !this._multiple && (typeof ngDevMode === 'undefined' || ngDevMode)) {
      throw getMultipleValuesInSingleSelectionError();
    }
  }

  /** 是否有列队的变更. */
  private _hasQueuedChanges() {
    return !!(this._deselectedToEmit.length || this._selectedToEmit.length);
  }
}

/**
 * 当SelectionModel的值发生变化时触发的事件.
 * @docs-private
 */
export interface SelectionChange<T> {
  source: SelectionModel<T>;
  added: T[];
  removed: T[];
}
// 不能以单值模式将多个值传递到SelectionModel
export function getMultipleValuesInSingleSelectionError() {
  return Error('Cannot pass multiple values into SelectionModel with single-value mode.');
}

应用场景很多,涉及到两种状态都可以用此模型,如 checkbox、选择框、raido等