Skip to content
This repository was archived by the owner on Oct 7, 2020. It is now read-only.

Commit fe306e0

Browse files
authored
fix(select): Compare objects with selected values (#2125)
closes #2058 closes #2141 closes #2035
1 parent b30f92c commit fe306e0

5 files changed

Lines changed: 105 additions & 63 deletions

File tree

demos/src/app/components/select/api.html

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ <h4 mdcSubtitle2>Properties</h4>
5151
<td>helperText: MdcHelperText</td>
5252
<td>Reference to related <code>MdcHelperText</code></td>
5353
</tr>
54+
<tr>
55+
<td>compareWith: (o1: any, o2: any) => boolean</td>
56+
<td>Function to compare the option values with the selected values. The
57+
first argument is a value from an option. The second is a value from
58+
the selection. A boolean should be returned.</td>
5459
</tbody>
5560
</table>
5661

@@ -59,7 +64,8 @@ <h4 mdcSubtitle2>Methods</h4>
5964
<tbody>
6065
<tr>
6166
<td>getSelectedIndex(): number</td>
62-
<td>Returns the index of the currently selected option. Returns -1 if no option is currently selected.</td>
67+
<td>Returns the index of the currently selected option. Returns -1 if no
68+
option is currently selected.</td>
6369
</tr>
6470
<tr>
6571
<td>setSelectionByValue(value)</td>
@@ -119,7 +125,8 @@ <h4 mdcSubtitle2>Properties</h4>
119125
<tbody>
120126
<tr>
121127
<td>validation: boolean</td>
122-
<td>Help text can be used to provide additional validation messages.</td>
128+
<td>Help text can be used to provide additional validation messages.
129+
</td>
123130
</tr>
124131
<tr>
125132
<td>persistent: boolean</td>

demos/src/app/components/select/examples.html

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@ <h3 class="demo-content__headline">Leading Icon</h3>
7373
'Off'}}</button>
7474
<button mdc-button (click)="meal.setSelectedIndex(2)">Set Index (2)</button>
7575
</div>
76-
<mdc-select #meal [helperText]="mealHelper" required placeholder="Pick a Meal">
76+
<mdc-select #meal [helperText]="mealHelper" required
77+
placeholder="Pick a Meal">
7778
<mdc-icon mdcSelectIcon>fastfood</mdc-icon>
7879
<mdc-menu class="demo-select-width">
7980
<mdc-list>
@@ -138,7 +139,8 @@ <h3 class="demo-content__headline">Custom Enhanced</h3>
138139
<div class="demo-content">
139140
<h3 class="demo-content__headline">Lazy Load</h3>
140141
<form [formGroup]="lazyLoadForm" #formDirectiveLazy="ngForm">
141-
<mdc-select outlined formControlName="lazySelect" [helperText]="lazyHelper" placeholder="Example">
142+
<mdc-select outlined formControlName="lazySelect" [helperText]="lazyHelper"
143+
placeholder="Example">
142144
<mdc-menu class="demo-select-width">
143145
<mdc-list>
144146
<mdc-list-item *ngFor="let food of lazyFoods" [value]="food.value"
@@ -161,17 +163,21 @@ <h3 class="demo-content__headline">Lazy Load</h3>
161163
<example-viewer [example]="exampleLazyLoaded"></example-viewer>
162164
</div>
163165

164-
<!-- <div class="demo-content">
166+
<div class="demo-content">
165167
<h3 class="demo-content__headline">Select with [compareWith]</h3>
166168
<form [formGroup]="formCompareWith">
167169
<mdc-select formControlName="fruit" [compareWith]="compareFn">
168-
<option *ngFor="let fruit of fruits" [ngValue]="fruit">
169-
{{ fruit.name }}
170-
</option>
170+
<mdc-menu class="demo-select-width">
171+
<mdc-list>
172+
<mdc-list-item *ngFor="let fruit of fruits" [value]="fruit">
173+
{{fruit.name}}</mdc-list-item>
174+
</mdc-list>
175+
</mdc-menu>
171176
</mdc-select>
172177
</form>
173178
<p> Value: {{ formCompareWith.value | json}} </p>
174-
</div> -->
179+
<example-viewer [example]="exampleCompareWith"></example-viewer>
180+
</div>
175181

176182
<div class="demo-content">
177183
<h3 class="demo-content__headline">Select with ngModel</h3>

demos/src/app/components/select/select.ts

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ interface Food {
99
disabled?: boolean;
1010
}
1111

12+
interface Fruit {
13+
id: number;
14+
name: string;
15+
}
16+
1217
@Component({template: '<component-viewer></component-viewer>'})
1318
export class Select implements OnInit {
1419
@ViewChild(ComponentViewer, {static: true}) _componentViewer: ComponentViewer;
@@ -47,24 +52,22 @@ export class Examples {
4752
foodControl = new FormControl();
4853

4954
foodForm = new FormGroup({
50-
favoriteFood: new FormControl(null, [Validators.required])
55+
favoriteFood: new FormControl('pizza-1', [Validators.required])
5156
});
5257

5358
lazyLoadForm = new FormGroup({
5459
lazySelect: new FormControl(null, [Validators.required])
5560
});
5661

5762
formCompareWith: FormGroup;
58-
fruits: object[] = [
63+
fruits: Fruit[] = [
5964
{
6065
id: 1,
61-
name: 'Pineapple',
62-
tasty: true
66+
name: 'Pineapple'
6367
},
6468
{
6569
id: 2,
66-
name: 'Watermelon',
67-
tasty: false
70+
name: 'Watermelon'
6871
}
6972
];
7073

@@ -76,13 +79,13 @@ export class Examples {
7679
{value: 'fruit-3', viewValue: 'Fruit'},
7780
];
7881

79-
compareFn(f1: any, f2: any): boolean {
82+
compareFn(f1: Fruit, f2: Fruit): boolean {
8083
return f1 && f2 ? f1.id === f2.id : f1 === f2;
8184
}
8285

8386
constructor(private fb: FormBuilder) {
8487
this.formCompareWith = this.fb.group({
85-
fruit: [undefined]
88+
fruit: this.fruits[0]
8689
});
8790

8891
this.foodControl.setValue('fruit-3');
@@ -289,6 +292,45 @@ resetForm(formDirective: FormGroupDirective) {
289292
}`
290293
};
291294

295+
exampleCompareWith = {
296+
html: `<form [formGroup]="formCompareWith">
297+
<mdc-select formControlName="fruit" [compareWith]="compareFn">
298+
<mdc-menu class="demo-select-width">
299+
<mdc-list>
300+
<mdc-list-item *ngFor="let fruit of fruits" [value]="fruit">{{fruit.name}}</mdc-list-item>
301+
</mdc-list>
302+
</mdc-menu>
303+
</mdc-select>
304+
</form>`,
305+
ts: `formCompareWith: FormGroup;
306+
307+
interface Fruit {
308+
id: number;
309+
name: string;
310+
}
311+
312+
fruits: Fruit[] = [
313+
{
314+
id: 1,
315+
name: 'Pineapple'
316+
},
317+
{
318+
id: 2,
319+
name: 'Watermelon'
320+
}
321+
];
322+
323+
constructor(private fb: FormBuilder) {
324+
this.formCompareWith = this.fb.group({
325+
fruit: [undefined]
326+
});
327+
}
328+
329+
compareFn(f1: Fruit, f2: Fruit): boolean {
330+
return f1 && f2 ? f1.id === f2.id : f1 === f2;
331+
}`
332+
};
333+
292334
exampleLazyLoaded = {
293335
html: `<form [formGroup]="lazyLoadForm" #formDirectiveLazy="ngForm">
294336
<mdc-select outlined formControlName="lazySelect" [helperText]="lazyHelper">

packages/select/select.ts

Lines changed: 18 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -206,14 +206,7 @@ export class MdcSelect extends _MdcSelectMixinBase implements AfterViewInit, DoC
206206
}
207207
private _valid?: boolean;
208208

209-
@Input()
210-
get compareWith() {
211-
return this._compareWith;
212-
}
213-
set compareWith(fn: (o1: any, o2: any) => boolean) {
214-
this._compareWith = fn;
215-
}
216-
private _compareWith = (o1: any, o2: any) => o1 === o2;
209+
@Input() compareWith: (o1: any, o2: any) => boolean = (a1, a2) => a1 === a2;
217210

218211
/** Value of the select */
219212
@Input()
@@ -390,6 +383,8 @@ export class MdcSelect extends _MdcSelectMixinBase implements AfterViewInit, DoC
390383
.then(() => {
391384
this._selectBuilder();
392385
});
386+
this._initializeSelection();
387+
this._subscribeToMenuEvents();
393388
}
394389

395390
ngOnDestroy(): void {
@@ -416,7 +411,9 @@ export class MdcSelect extends _MdcSelectMixinBase implements AfterViewInit, DoC
416411
}
417412

418413
writeValue(value: any): void {
419-
this.setSelectionByValue(value, false);
414+
if (this._initialized) {
415+
this.setSelectionByValue(value, false);
416+
}
420417
}
421418

422419
registerOnChange(fn: (value: any) => void): void {
@@ -455,23 +452,20 @@ export class MdcSelect extends _MdcSelectMixinBase implements AfterViewInit, DoC
455452
}
456453

457454
setSelectedIndex(index: number): void {
458-
this._foundation.setSelectedIndex(index, this._menu.closeSurfaceOnSelection);
459-
this.setSelectionByValue(this._menu._list?.getListItemByIndex(index)?.value);
455+
this.setSelectionByValue(this._menu._list?.getListItemByIndex(index)?.value, true, index);
460456
}
461457

462458
setSelectionByValue(value: any, isUserInput = true, index?: number): void {
463459
if (!this._value && !value) {
464460
return;
465461
}
466462

467-
if (index) {
468-
// This method was called directly, not called from setSelectedIndex
469-
this._foundation.setSelectedIndex(index, this._menu.closeSurfaceOnSelection);
470-
value = this._menu._list?.getListItemByIndex(index)?.value;
463+
if (!index) {
464+
index = this._menu._list?.getListItemIndexByValue(value);
471465
}
472466

473467
this._value = value;
474-
this._foundation?.setValue(this._value);
468+
this._foundation.setSelectedIndex(index ?? -1, this._menu.closeSurfaceOnSelection);
475469

476470
if (isUserInput) {
477471
this._onChange(this._value);
@@ -482,15 +476,13 @@ export class MdcSelect extends _MdcSelectMixinBase implements AfterViewInit, DoC
482476
index: this.getSelectedIndex(),
483477
value: this._value
484478
});
485-
this._changeDetectorRef.markForCheck();
486-
}
487479

488-
async _asyncSetSelectionByValue(value: any, isUserInput: boolean = true): Promise<void> {
489-
return this.setSelectionByValue(value, isUserInput);
480+
this._foundation.handleChange();
481+
this._changeDetectorRef.markForCheck();
490482
}
491483

492484
// Implemented as part of ControlValueAccessor.
493-
setDisabledState(disabled: boolean) {
485+
setDisabledState(disabled: boolean): void {
494486
this._disabled = coerceBooleanProperty(disabled);
495487
this._foundation?.setDisabled(this._disabled);
496488
this._changeDetectorRef.markForCheck();
@@ -503,11 +495,10 @@ export class MdcSelect extends _MdcSelectMixinBase implements AfterViewInit, DoC
503495
/** Initialize Select internal state based on the environment state */
504496
private layout(): void {
505497
if (this._initialized) {
506-
this._selectBuilder();
507-
508-
if (this._outlined) {
498+
this._asyncBuildFoundation().then(() => {
499+
this._selectBuilder();
509500
this._foundation.layout();
510-
}
501+
});
511502
}
512503
}
513504

@@ -518,7 +509,6 @@ export class MdcSelect extends _MdcSelectMixinBase implements AfterViewInit, DoC
518509
const value = this.ngControl?.value ?? this._value;
519510
if (value) {
520511
this.setSelectionByValue(value, false);
521-
this._foundation.layout();
522512
}
523513
});
524514
}
@@ -543,14 +533,13 @@ export class MdcSelect extends _MdcSelectMixinBase implements AfterViewInit, DoC
543533
private _selectBuilder(): void {
544534
this._changeDetectorRef.detectChanges();
545535

536+
// initialize after running a detectChanges()
546537
this._asyncInitFoundation()
547538
.then(() => {
548-
// initialize after running a detectChanges()
549539
if (!this.outlined) {
550540
this._ripple = this._createRipple();
551541
this._ripple.init();
552542
}
553-
this._initializeSelection();
554543

555544
this._menu.wrapFocus = false;
556545
this._menu.elementRef.nativeElement.setAttribute('role', 'listbox');
@@ -561,13 +550,12 @@ export class MdcSelect extends _MdcSelectMixinBase implements AfterViewInit, DoC
561550
this._menu._list.singleSelection = true;
562551
}
563552
});
564-
this._subscribeToMenuEvents();
565553
}
566554

567555
private _subscribeToMenuEvents(): void {
568556
// When the list items change, re-subscribe
569557
this._menu._list!.items.changes.pipe(takeUntil(this._destroyed))
570-
.subscribe(() => this.layout());
558+
.subscribe(() => this._initializeSelection());
571559

572560
// Subscribe to menu opened event
573561
this._menu.opened.pipe(takeUntil(this._destroyed))

0 commit comments

Comments
 (0)