Skip to content

Commit 788a6d4

Browse files
authored
fix(option): emit selection change when selection is changed (#1334)
1 parent a7a158d commit 788a6d4

File tree

3 files changed

+237
-51
lines changed

3 files changed

+237
-51
lines changed

src/framework/theme/components/select/option.component.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
OnDestroy,
1919
Output,
2020
} from '@angular/core';
21+
import { Observable, Subject } from 'rxjs';
2122
import { convertToBoolProperty } from '../helpers';
2223
import { NbSelectComponent } from './select.component';
2324

@@ -52,10 +53,18 @@ export class NbOptionComponent<T> implements OnDestroy {
5253
}
5354

5455
/**
55-
* Fires value on click.
56+
* Fires value when option selection change.
5657
* */
5758
@Output() selectionChange: EventEmitter<NbOptionComponent<T>> = new EventEmitter();
5859

60+
/**
61+
* Fires when option clicked
62+
*/
63+
private click$: Subject<NbOptionComponent<T>> = new Subject<NbOptionComponent<T>>();
64+
get click(): Observable<NbOptionComponent<T>> {
65+
return this.click$.asObservable();
66+
}
67+
5968
selected: boolean = false;
6069
disabled: boolean = false;
6170
private alive: boolean = true;
@@ -96,7 +105,7 @@ export class NbOptionComponent<T> implements OnDestroy {
96105

97106
@HostListener('click')
98107
onClick() {
99-
this.selectionChange.emit(this);
108+
this.click$.next(this);
100109
}
101110

102111
select() {
@@ -107,18 +116,18 @@ export class NbOptionComponent<T> implements OnDestroy {
107116
this.setSelection(false);
108117
}
109118

110-
private setSelection(isSelected: boolean): void {
119+
private setSelection(selected: boolean): void {
111120
/**
112121
* In case of changing options in runtime the reference to the selected option will be kept in select component.
113122
* This may lead to exceptions with detecting changes in destroyed component.
114123
*
115124
* Also Angular can call writeValue on destroyed view (select implements ControlValueAccessor).
116125
* angular/angular#27803
117126
* */
118-
if (this.alive) {
119-
this.selected = isSelected;
127+
if (this.alive && this.selected !== selected) {
128+
this.selected = selected;
129+
this.selectionChange.emit(this);
120130
this.cd.markForCheck();
121131
}
122132
}
123133
}
124-

src/framework/theme/components/select/select.component.ts

Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import {
2626
} from '@angular/core';
2727
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
2828
import { merge, Observable, Subject } from 'rxjs';
29-
import { takeWhile } from 'rxjs/operators';
29+
import { startWith, switchMap, takeWhile } from 'rxjs/operators';
3030

3131
import {
3232
NbAdjustableConnectedPositionStrategy,
@@ -252,10 +252,13 @@ export class NbSelectComponent<T> implements OnInit, AfterViewInit, AfterContent
252252
*/
253253
overlayPosition: NbPosition = '' as NbPosition;
254254

255-
/**
256-
* Stream of events that will fire when one of the options fire selectionChange event.
257-
* */
258255
protected selectionChange$: Subject<NbOptionComponent<T>> = new Subject();
256+
/**
257+
* Stream of events that will fire when one of the options is clicked.
258+
* @deprecated
259+
* Use nb-select (selected) binding to track selection change and <nb-option (click)=""> to track option click.
260+
* @breaking-change 4.0.0
261+
**/
259262
readonly selectionChange: Observable<NbOptionComponent<T>> = this.selectionChange$.asObservable();
260263

261264
protected ref: NbOverlayRef;
@@ -339,7 +342,7 @@ export class NbSelectComponent<T> implements OnInit, AfterViewInit, AfterContent
339342

340343
this.subscribeOnTriggers();
341344
this.subscribeOnPositionChange();
342-
this.subscribeOnSelectionChange();
345+
this.subscribeOnOptionClick();
343346
}
344347

345348
ngOnDestroy() {
@@ -395,7 +398,7 @@ export class NbSelectComponent<T> implements OnInit, AfterViewInit, AfterContent
395398
/**
396399
* Selects option or clear all selected options if value is null.
397400
* */
398-
protected handleSelect(option: NbOptionComponent<T>) {
401+
protected handleOptionClick(option: NbOptionComponent<T>) {
399402
if (option.value) {
400403
this.selectOption(option);
401404
} else {
@@ -505,19 +508,24 @@ export class NbSelectComponent<T> implements OnInit, AfterViewInit, AfterContent
505508
});
506509
}
507510

508-
protected subscribeOnSelectionChange() {
509-
this.subscribeOnOptionsSelectionChange();
511+
protected subscribeOnOptionClick() {
510512
/**
511513
* If the user changes provided options list in the runtime we have to handle this
512514
* and resubscribe on options selection changes event.
513515
* Otherwise, the user will not be able to select new options.
514516
* */
515517
this.options.changes
516-
.subscribe(() => this.subscribeOnOptionsSelectionChange());
517-
518-
this.selectionChange
519-
.pipe(takeWhile(() => this.alive))
520-
.subscribe((option: NbOptionComponent<T>) => this.handleSelect(option));
518+
.pipe(
519+
startWith(this.options),
520+
switchMap((options: QueryList<NbOptionComponent<T>>) => {
521+
return merge(...options.map(option => option.click));
522+
}),
523+
takeWhile(() => this.alive),
524+
)
525+
.subscribe((clickedOption: NbOptionComponent<T>) => {
526+
this.handleOptionClick(clickedOption);
527+
this.selectionChange$.next(clickedOption);
528+
});
521529
}
522530

523531
protected getContainer() {
@@ -550,20 +558,21 @@ export class NbSelectComponent<T> implements OnInit, AfterViewInit, AfterContent
550558
throw new Error('Can\'t assign array if select is not marked as multiple');
551559
}
552560

553-
this.cleanSelection();
561+
const previouslySelectedOptions = this.selectionModel;
562+
this.selectionModel = [];
554563

555564
if (isArray) {
556565
(<T[]> value).forEach((option: T) => this.selectValue(option));
557566
} else {
558567
this.selectValue(<T> value);
559568
}
560569

561-
this.cd.markForCheck();
562-
}
570+
// find options which were selected before and trigger deselect
571+
previouslySelectedOptions
572+
.filter((option: NbOptionComponent<T>) => !this.selectionModel.includes(option))
573+
.forEach((option: NbOptionComponent<T>) => option.deselect());
563574

564-
protected cleanSelection() {
565-
this.selectionModel.forEach((option: NbOptionComponent<T>) => option.deselect());
566-
this.selectionModel = [];
575+
this.cd.markForCheck();
567576
}
568577

569578
/**
@@ -591,10 +600,4 @@ export class NbSelectComponent<T> implements OnInit, AfterViewInit, AfterContent
591600
protected isClickedWithinComponent($event: Event) {
592601
return this.hostRef.nativeElement === $event.target || this.hostRef.nativeElement.contains($event.target as Node);
593602
}
594-
595-
protected subscribeOnOptionsSelectionChange() {
596-
merge(...this.options.map(it => it.selectionChange))
597-
.pipe(takeWhile(() => this.alive))
598-
.subscribe((change: NbOptionComponent<T>) => this.selectionChange$.next(change));
599-
}
600603
}

0 commit comments

Comments
 (0)