Emissores e assuntos personalizados em Angular: Encapsulando Lógica de Alternância e MultiSelect

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.








All Articles