diff --git a/src/cdk/listbox/listbox.spec.ts b/src/cdk/listbox/listbox.spec.ts
index 82f41e22b1f7..c9ac18836a01 100644
--- a/src/cdk/listbox/listbox.spec.ts
+++ b/src/cdk/listbox/listbox.spec.ts
@@ -883,6 +883,18 @@ describe('CdkOption and CdkListbox', () => {
fixture.detectChanges();
}).toThrowError('Listbox has selected values that do not match any of its options.');
});
+
+ it('should not throw on init with a preselected form control and a dynamic set of options', () => {
+ expect(() => {
+ setupComponent(ListboxWithPreselectedFormControl, [ReactiveFormsModule]);
+ }).not.toThrow();
+ });
+
+ it('should throw on init if the preselected value is invalid', () => {
+ expect(() => {
+ setupComponent(ListboxWithInvalidPreselectedFormControl, [ReactiveFormsModule]);
+ }).toThrowError('Listbox has selected values that do not match any of its options.');
+ });
});
});
@@ -955,6 +967,30 @@ class ListboxWithFormControl {
isActiveDescendant = false;
}
+@Component({
+ template: `
+
+ `,
+})
+class ListboxWithPreselectedFormControl {
+ options = ['a', 'b', 'c'];
+ formControl = new FormControl('c');
+}
+
+@Component({
+ template: `
+
+ `,
+})
+class ListboxWithInvalidPreselectedFormControl {
+ options = ['a', 'b', 'c'];
+ formControl = new FormControl('d');
+}
+
@Component({
template: `
diff --git a/src/cdk/listbox/listbox.ts b/src/cdk/listbox/listbox.ts
index 697de2281e88..38575e067d5e 100644
--- a/src/cdk/listbox/listbox.ts
+++ b/src/cdk/listbox/listbox.ts
@@ -422,6 +422,7 @@ export class CdkListbox implements AfterContentInit, OnDestroy, Con
ngAfterContentInit() {
if (typeof ngDevMode === 'undefined' || ngDevMode) {
this._verifyNoOptionValueCollisions();
+ this._verifyOptionValues();
}
this._initKeyManager();
@@ -561,19 +562,7 @@ export class CdkListbox implements AfterContentInit, OnDestroy, Con
*/
writeValue(value: readonly T[]): void {
this._setSelection(value);
-
- if (typeof ngDevMode === 'undefined' || ngDevMode) {
- const selected = this.selectionModel.selected;
- const invalidValues = this._getInvalidOptionValues(selected);
-
- if (!this.multiple && selected.length > 1) {
- throw Error('Listbox cannot have more than one selected value in multi-selection mode.');
- }
-
- if (invalidValues.length) {
- throw Error('Listbox has selected values that do not match any of its options.');
- }
- }
+ this._verifyOptionValues();
}
/**
@@ -924,6 +913,22 @@ export class CdkListbox implements AfterContentInit, OnDestroy, Con
});
}
+ /** Verifies that the option values are valid. */
+ private _verifyOptionValues() {
+ if (this.options && (typeof ngDevMode === 'undefined' || ngDevMode)) {
+ const selected = this.selectionModel.selected;
+ const invalidValues = this._getInvalidOptionValues(selected);
+
+ if (!this.multiple && selected.length > 1) {
+ throw Error('Listbox cannot have more than one selected value in multi-selection mode.');
+ }
+
+ if (invalidValues.length) {
+ throw Error('Listbox has selected values that do not match any of its options.');
+ }
+ }
+ }
+
/**
* Coerces a value into an array representing a listbox selection.
* @param value The value to coerce