Benutzerdefinierte Emitter und Motive in Angular: Encapsulating Toggle und MultiSelect Logic

In großen Angular-Projekten können Sie häufig sich wiederholendes Verhalten in Komponenten feststellen. Es ist wünschenswert, dieses Verhalten aus der Komponente in separate Klassen zu verschieben, die wiederverwendet werden können. Ich werde zwei ziemlich beliebte Fälle betrachten: Switch und Multiple Choice von Entitäten.





Fall 1: Umschalten

Oft sieht man im Quellcode so etwas:





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



oder so:





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





.





Ich würde mich freuen, Ihre Ideen in den Kommentaren zu haben. Konstruktive Kritik wird angeregt.








All Articles