Em grandes projetos Angular, você geralmente pode ver o comportamento repetitivo nos componentes. É desejável mover esse comportamento do componente para classes separadas que podem ser reutilizadas. Vou considerar dois casos bastante populares: switch e múltipla escolha de entidades.
Caso 1: Alternar
Muitas vezes, no código-fonte, você vê algo assim:
export class SampleComponent {
@Output somethingSelected = new EventEmitter<boolean>()
...
private _selected = false;
toggleSelected() {
this._selected = !this._selected;
this.somethingSelected.emit(this._selected);
}
}
ou assim:
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(({checked}) => checked).map(({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 } : v));
}
setCheckedForAll(checked: boolean) {
this.list = this.list.map(v => ({...v, checked}));
}
next(v: T[]) {
this.eventEmitter.emit(v);
super.next(v);
}
}
:
export class SampleComponent {
@Input() set data(v: SampleDto[]) {
this.multiSelector = new EntityMultiSelector<SampleDto>(v);
this.selectedSamples = this.multiSelector.eventEmitter;
}
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>
:
https://stackblitz.com/edit/angular-ivy-kyaeac?file=src/app/app.component.html
.
Eu ficaria feliz em ter suas idéias nos comentários. A crítica construtiva é encorajada.