Skip to content

Commit

Permalink
feat(select): add custom comparator input (#2590)
Browse files Browse the repository at this point in the history
  • Loading branch information
ascripcaru committed Dec 16, 2020
1 parent a26bd70 commit 1f8a57b
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 5 deletions.
6 changes: 6 additions & 0 deletions src/app/playground-components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1119,6 +1119,12 @@ export const PLAYGROUND_COMPONENTS: ComponentLink[] = [
component: 'SelectTestComponent',
name: 'Select Test',
},
{
path: 'select-compare-with.component',
link: '/select/select-compare-with.component',
component: 'SelectCompareWithComponent',
name: 'Select Compare With',
},
],
},
{
Expand Down
35 changes: 31 additions & 4 deletions src/framework/theme/components/select/select.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import { NB_SELECT_INJECTION_TOKEN } from './select-injection-tokens';
import { NbFormFieldControl, NbFormFieldControlConfig } from '../form-field/form-field-control';
import { NbFocusMonitor } from '../cdk/a11y/a11y.module';

export type NbSelectCompareFunction<T = any> = (v1: any, v2: any) => boolean;
export type NbSelectAppearance = 'outline' | 'filled' | 'hero';

@Component({
Expand Down Expand Up @@ -144,6 +145,11 @@ export function nbSelectFormFieldControlConfigFactory() {
*
* @stacked-example(Select shapes, select/select-shapes.component)
*
* By default, the component selects options whose values are strictly equal (`===`) with the select value.
* To change such behavior, pass a custom comparator function to the `compareWith` attribute.
*
* @stacked-example(Select custom comparator, select/select-compare-with.component)
*
* @additional-example(Interactive, select/select-interactive.component)
*
* @styles
Expand Down Expand Up @@ -508,7 +514,7 @@ export function nbSelectFormFieldControlConfigFactory() {
],
})
export class NbSelectComponent implements OnChanges, AfterViewInit, AfterContentInit, OnDestroy,
ControlValueAccessor, NbFormFieldControl {
ControlValueAccessor, NbFormFieldControl {

/**
* Select size, available sizes:
Expand Down Expand Up @@ -609,6 +615,27 @@ export class NbSelectComponent implements OnChanges, AfterViewInit, AfterContent
* */
@Input() placeholder: string = '';

/**
* A function to compare option value with selected value.
* By default, values are compared with strict equality (`===`).
*/
@Input()
get compareWith(): NbSelectCompareFunction {
return this._compareWith;
}
set compareWith(fn: NbSelectCompareFunction) {
if (typeof fn !== 'function') {
return;
}

this._compareWith = fn;

if (this.selectionModel.length && this.canSelectValue()) {
this.setSelection(this.selected);
}
}
protected _compareWith: NbSelectCompareFunction = (v1: any, v2: any) => v1 === v2;

/**
* Accepts selected item or array of selected items.
* */
Expand Down Expand Up @@ -926,7 +953,7 @@ export class NbSelectComponent implements OnChanges, AfterViewInit, AfterContent
protected handleSingleSelect(option: NbOptionComponent) {
const selected = this.selectionModel.pop();

if (selected && selected !== option) {
if (selected && !this._compareWith(selected.value, option.value)) {
selected.deselect();
}

Expand All @@ -943,7 +970,7 @@ export class NbSelectComponent implements OnChanges, AfterViewInit, AfterContent
* */
protected handleMultipleSelect(option: NbOptionComponent) {
if (option.selected) {
this.selectionModel = this.selectionModel.filter(s => s.value !== option.value);
this.selectionModel = this.selectionModel.filter(s => !this._compareWith(s.value, option.value));
option.deselect();
} else {
this.selectionModel.push(option);
Expand Down Expand Up @@ -1124,7 +1151,7 @@ export class NbSelectComponent implements OnChanges, AfterViewInit, AfterContent
* Selects value.
* */
protected selectValue(value) {
const corresponding = this.options.find((option: NbOptionComponent) => option.value === value);
const corresponding = this.options.find((option: NbOptionComponent) => this._compareWith(option.value, value));

if (corresponding) {
corresponding.select();
Expand Down
33 changes: 32 additions & 1 deletion src/framework/theme/components/select/select.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ const TEST_GROUPS = [
{ title: 'Option 41', value: '' },
{ title: 'Option 42', value: '0' },
{ title: 'Option 43', value: 0 },
{ title: 'Option 44'},
{ title: 'Option 44' },
],
},
];
Expand Down Expand Up @@ -120,6 +120,25 @@ export class NbSelectTestComponent {
})
export class BasicSelectTestComponent {}

@Component({
template: `
<nb-layout>
<nb-layout-column>
<nb-select [selected]="selected" [compareWith]="compareFn">
<nb-option *ngFor="let option of options" [value]="option">{{ option }}</nb-option>
</nb-select>
</nb-layout-column>
</nb-layout>
`,
})
export class NbSelectWithOptionsObjectsComponent {
@Input() compareFn = (o1: any, o2: any) => JSON.stringify(o1) === JSON.stringify(o2);
@Input() selected = { id: 2 };
@Input() options = [{ id: 1 }, { id: 2 }, { id: 3 }];

@ViewChildren(NbOptionComponent) optionComponents: QueryList<NbOptionComponent>;
}

@Component({
template: `
<nb-layout>
Expand Down Expand Up @@ -325,6 +344,7 @@ describe('Component: NbSelectComponent', () => {
],
declarations: [
NbSelectTestComponent,
NbSelectWithOptionsObjectsComponent,
NbSelectWithInitiallySelectedOptionComponent,
NbReactiveFormSelectComponent,
NbNgModelSelectComponent,
Expand Down Expand Up @@ -495,6 +515,17 @@ describe('Component: NbSelectComponent', () => {
expect(selectButton.textContent).toEqual(selectedOption.value.toString());
}));

it('should use compareWith function to compare values', fakeAsync(() => {
const selectFixture = TestBed.createComponent(NbSelectWithOptionsObjectsComponent);
const testComponent = selectFixture.componentInstance;
selectFixture.detectChanges();
flush();
selectFixture.detectChanges();

const selectedOption = testComponent.optionComponents.find(o => o.selected);
expect(selectedOption.value).toEqual({ id: 2 });
}));

it('should ignore selection change if destroyed', fakeAsync(() => {
const selectFixture = TestBed.createComponent(NbReactiveFormSelectComponent);
const testSelectComponent = selectFixture.componentInstance;
Expand Down
27 changes: 27 additions & 0 deletions src/playground/with-layout/select/select-compare-with.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* @license
* Copyright Akveo. All Rights Reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*/

import { ChangeDetectionStrategy, Component } from '@angular/core';

@Component({
template: `
<nb-card size="small">
<nb-card-body>
<nb-select [selected]="{ id: 1 }" [compareWith]="compareById">
<nb-option [value]="{ id: 1 }">1</nb-option>
<nb-option [value]="{ id: 2 }">2</nb-option>
<nb-option [value]="{ id: 3 }">3</nb-option>
</nb-select>
</nb-card-body>
</nb-card>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SelectCompareWithComponent {
compareById(v1, v2): boolean {
return v1.id === v2.id;
}
}
5 changes: 5 additions & 0 deletions src/playground/with-layout/select/select-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { SelectSizesComponent } from './select-sizes.component';
import { SelectStatusComponent } from './select-status.component';
import { SelectInteractiveComponent } from './select-interactive.component';
import { SelectTestComponent } from './select-test.component';
import { SelectCompareWithComponent } from './select-compare-with.component';

const routes: Route[] = [
{
Expand Down Expand Up @@ -83,6 +84,10 @@ const routes: Route[] = [
path: 'select-test.component',
component: SelectTestComponent,
},
{
path: 'select-compare-with.component',
component: SelectCompareWithComponent,
},
];

@NgModule({
Expand Down
2 changes: 2 additions & 0 deletions src/playground/with-layout/select/select.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { SelectSizesComponent } from './select-sizes.component';
import { SelectStatusComponent } from './select-status.component';
import { SelectInteractiveComponent } from './select-interactive.component';
import { SelectTestComponent } from './select-test.component';
import { SelectCompareWithComponent } from './select-compare-with.component';

@NgModule({
declarations: [
Expand All @@ -42,6 +43,7 @@ import { SelectTestComponent } from './select-test.component';
SelectStatusComponent,
SelectInteractiveComponent,
SelectTestComponent,
SelectCompareWithComponent,
],
imports: [
FormsModule,
Expand Down

0 comments on commit 1f8a57b

Please sign in to comment.