Skip to content

Commit 166ed5e

Browse files
authored
fix(cdk/listbox): incorrectly validating preselected value (#25893)
In #25856 the check that verifies the validity of the form control value was moved into `writeValue`. This works as expected for assignments after initialization, but it throws an error incorrectly if there is a preselected value, because `writeValue` will be called before the options are available. These changes resolve the issue by checking that the options have been initialized before throwing the error.
1 parent 8304a94 commit 166ed5e

File tree

2 files changed

+54
-13
lines changed

2 files changed

+54
-13
lines changed

src/cdk/listbox/listbox.spec.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -883,6 +883,18 @@ describe('CdkOption and CdkListbox', () => {
883883
fixture.detectChanges();
884884
}).toThrowError('Listbox has selected values that do not match any of its options.');
885885
});
886+
887+
it('should not throw on init with a preselected form control and a dynamic set of options', () => {
888+
expect(() => {
889+
setupComponent(ListboxWithPreselectedFormControl, [ReactiveFormsModule]);
890+
}).not.toThrow();
891+
});
892+
893+
it('should throw on init if the preselected value is invalid', () => {
894+
expect(() => {
895+
setupComponent(ListboxWithInvalidPreselectedFormControl, [ReactiveFormsModule]);
896+
}).toThrowError('Listbox has selected values that do not match any of its options.');
897+
});
886898
});
887899
});
888900

@@ -955,6 +967,30 @@ class ListboxWithFormControl {
955967
isActiveDescendant = false;
956968
}
957969

970+
@Component({
971+
template: `
972+
<div cdkListbox [formControl]="formControl">
973+
<div *ngFor="let option of options" [cdkOption]="option">{{option}}</div>
974+
</div>
975+
`,
976+
})
977+
class ListboxWithPreselectedFormControl {
978+
options = ['a', 'b', 'c'];
979+
formControl = new FormControl('c');
980+
}
981+
982+
@Component({
983+
template: `
984+
<div cdkListbox [formControl]="formControl">
985+
<div *ngFor="let option of options" [cdkOption]="option">{{option}}</div>
986+
</div>
987+
`,
988+
})
989+
class ListboxWithInvalidPreselectedFormControl {
990+
options = ['a', 'b', 'c'];
991+
formControl = new FormControl('d');
992+
}
993+
958994
@Component({
959995
template: `
960996
<ul cdkListbox>

src/cdk/listbox/listbox.ts

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,7 @@ export class CdkListbox<T = unknown> implements AfterContentInit, OnDestroy, Con
422422
ngAfterContentInit() {
423423
if (typeof ngDevMode === 'undefined' || ngDevMode) {
424424
this._verifyNoOptionValueCollisions();
425+
this._verifyOptionValues();
425426
}
426427

427428
this._initKeyManager();
@@ -561,19 +562,7 @@ export class CdkListbox<T = unknown> implements AfterContentInit, OnDestroy, Con
561562
*/
562563
writeValue(value: readonly T[]): void {
563564
this._setSelection(value);
564-
565-
if (typeof ngDevMode === 'undefined' || ngDevMode) {
566-
const selected = this.selectionModel.selected;
567-
const invalidValues = this._getInvalidOptionValues(selected);
568-
569-
if (!this.multiple && selected.length > 1) {
570-
throw Error('Listbox cannot have more than one selected value in multi-selection mode.');
571-
}
572-
573-
if (invalidValues.length) {
574-
throw Error('Listbox has selected values that do not match any of its options.');
575-
}
576-
}
565+
this._verifyOptionValues();
577566
}
578567

579568
/**
@@ -924,6 +913,22 @@ export class CdkListbox<T = unknown> implements AfterContentInit, OnDestroy, Con
924913
});
925914
}
926915

916+
/** Verifies that the option values are valid. */
917+
private _verifyOptionValues() {
918+
if (this.options && (typeof ngDevMode === 'undefined' || ngDevMode)) {
919+
const selected = this.selectionModel.selected;
920+
const invalidValues = this._getInvalidOptionValues(selected);
921+
922+
if (!this.multiple && selected.length > 1) {
923+
throw Error('Listbox cannot have more than one selected value in multi-selection mode.');
924+
}
925+
926+
if (invalidValues.length) {
927+
throw Error('Listbox has selected values that do not match any of its options.');
928+
}
929+
}
930+
}
931+
927932
/**
928933
* Coerces a value into an array representing a listbox selection.
929934
* @param value The value to coerce

0 commit comments

Comments
 (0)