手风琴是具有一个或多个可展开分区的组件。 CDK accordion提供了一个基础,你可以在此基础上构建自己的自定义手风琴组件。 CDK 手风琴为手风琴交互模式提供了逻辑,没有任何样式。你可以根据其能力一次封闭成自己想要的手风琴的外观
CdkAccordion
import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion';
import { Directive, InjectionToken, Input, OnChanges, OnDestroy, SimpleChanges } from '@angular/core';
import { Subject } from 'rxjs';
/** 用于为每个手风琴生成唯一的ID */
let nextId = 0;
/**
* 用来引用' CdkAccordion '的实例
*/
export const CDK_ACCORDION = new InjectionToken<CdkAccordion>('CdkAccordion');
/**
* 手风琴指令 父级对象,用来管理CdkAccordionItem子对象的状态.
*/
@Directive({
selector: 'cdk-accordion, [cdkAccordion]',
exportAs: 'cdkAccordion',
providers: [{ provide: CDK_ACCORDION, useExisting: CdkAccordion }],
})
export class CdkAccordion implements OnDestroy, OnChanges {
/** 当 CdkAccordion 状态更改是发出*/
readonly _stateChanges = new Subject<SimpleChanges>();
/** 当 openAll/closeAll 被触发时 发送其状态. */
readonly _openCloseAllActions: Subject<boolean> = new Subject<boolean>();
/** 唯一的id. 用于区分父级*/
readonly id: string = `cdk-accordion-${nextId++}`;
/** 是否允许多个展开的手风琴项. */
@Input()
get multi(): boolean {
return this._multi;
}
set multi(multi: BooleanInput) {
this._multi = coerceBooleanProperty(multi);
}
private _multi: boolean = false;
/** 当multi为true时accordion中展开所有已启用的CdkAccordionItem */
openAll(): void {
if (this._multi) {
this._openCloseAllActions.next(true);
}
}
/** 在accordion中关闭所有CdkAccordionItem */
closeAll(): void {
this._openCloseAllActions.next(false);
}
ngOnChanges(changes: SimpleChanges) {
this._stateChanges.next(changes);
}
ngOnDestroy() {
this._stateChanges.complete();
this._openCloseAllActions.complete();
}
}
CDKAccordion是一个父容器,提供一个统一管理CDKAccordionItem指令中控中心
CdkAccordionItem
import {
Output,
Directive,
EventEmitter,
Input,
OnDestroy,
Optional,
ChangeDetectorRef,
SkipSelf,
Inject,
} from '@angular/core';
import { UniqueSelectionDispatcher } from '@angular/cdk/collections';
import { CDK_ACCORDION, CdkAccordion } from './accordion';
import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion';
import { Subscription } from 'rxjs';
/** 为每一个accordion item 生成唯一id. */
let nextId = 0;
/**
* 事件和属性需要由CdkAccordion父节点管理
*/
@Directive({
selector: 'cdk-accordion-item, [cdkAccordionItem]',
exportAs: 'cdkAccordionItem',
providers: [
// 提供' CDK_ACCORDION '作为未定义的,以防止嵌套的cdkAccordionItem
// 让其注册到相同的accordion
{ provide: CDK_ACCORDION, useValue: undefined },
],
})
export class CdkAccordionItem implements OnDestroy {
/** 订阅父级的_openCloseAllActions事件. */
private _openCloseAllSubscription = Subscription.EMPTY;
/** 每次AccordionItem关闭时触发的事件. */
@Output() readonly closed: EventEmitter<void> = new EventEmitter<void>();
/** 每次AccordionItem打开时触发的事件. */
@Output() readonly opened: EventEmitter<void> = new EventEmitter<void>();
/** 当AccordionItem被销毁时触发的事件. */
@Output() readonly destroyed: EventEmitter<void> = new EventEmitter<void>();
/**
* 当 属性 expanded 状态更改时触发的事件
*/
@Output() readonly expandedChange: EventEmitter<boolean> = new EventEmitter<boolean>();
/** 指令的唯一id. */
readonly id: string = `cdk-accordion-child-${nextId++}`;
/** AccordionItem 展开状态 true 为展开. */
@Input()
get expanded(): boolean {
return this._expanded;
}
set expanded(expanded: BooleanInput) {
expanded = coerceBooleanProperty(expanded);
// 当expanded状态更改才更新状态,触发状态更改事件.
if (this._expanded !== expanded) {
this._expanded = expanded;
this.expandedChange.emit(expanded);
if (expanded) {
this.opened.emit();
// 派发 当前和父级id,通知所有监听订阅的事件,用于在父级只允许一个展开一个cdkAccordionItem时,打开当前 关闭其它cdkAccordionItem
const accordionId = this.accordion ? this.accordion.id : this.id;
this._expansionDispatcher.notify(this.id, accordionId);
} else {
this.closed.emit();
}
this._changeDetectorRef.markForCheck();
}
}
private _expanded = false;
/** AccordionItem是否被禁用. */
@Input()
get disabled(): boolean {
return this._disabled;
}
set disabled(disabled: BooleanInput) {
this._disabled = coerceBooleanProperty(disabled);
}
private _disabled = false;
/** 用于取消_expansionDispatcher的注册的函数. */
private _removeUniqueSelectionListener: () => void = () => {};
constructor(
// 获取相同的父级引用
@Optional() @Inject(CDK_ACCORDION) @SkipSelf() public accordion: CdkAccordion,
private _changeDetectorRef: ChangeDetectorRef,
protected _expansionDispatcher: UniqueSelectionDispatcher,
) {
this._removeUniqueSelectionListener = _expansionDispatcher.listen((id: string, accordionId: string) => {
// 关闭父级其它cdkAccordionItem
if (this.accordion && !this.accordion.multi && this.accordion.id === accordionId && this.id !== id) {
this.expanded = false;
}
});
// 当一个AccordionItem托管在一个accordion中时,订阅打开/关闭事件.
if (this.accordion) {
this._openCloseAllSubscription = this._subscribeToOpenCloseAllActions();
}
}
/** 销毁accordionItemt每个事件. */
ngOnDestroy() {
this.opened.complete();
this.closed.complete();
this.destroyed.emit();
this.destroyed.complete();
this._removeUniqueSelectionListener();
this._openCloseAllSubscription.unsubscribe();
}
/** 切换展开状态. */
toggle(): void {
if (!this.disabled) {
this.expanded = !this.expanded;
}
}
/** 设置展开状态为false. */
close(): void {
if (!this.disabled) {
this.expanded = false;
}
}
/** 设置展开状态为true. */
open(): void {
if (!this.disabled) {
this.expanded = true;
}
}
private _subscribeToOpenCloseAllActions(): Subscription {
return this.accordion._openCloseAllActions.subscribe(expanded => {
// 用于子级的全部展开或关闭
if (!this.disabled) {
this.expanded = expanded;
}
});
}
}
accordionItem单独的状态管理,每个accordionItem之间的联动通过accordion托管状态更改