Émetteurs et sujets personnalisés en angulaire: encapsulation de la logique à bascule et MultiSelect

Dans les grands projets angulaires, vous pouvez souvent voir un comportement répétitif dans les composants. Il est souhaitable de déplacer ce comportement du composant vers des classes distinctes pouvant être réutilisées. Je vais considérer deux cas assez courants: le commutateur et le choix multiple d'entités.





Cas 1: bascule

Souvent, dans le code source, vous voyez quelque chose comme ceci:





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



ou comme ça:





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





.





Je serais ravi d'avoir vos idées dans les commentaires. La critique constructive est encouragée.








All Articles