fix(chip-list): fix error state changes in chip list (#8425)

This commit is contained in:
tinayuangao
2017-11-28 11:48:28 -08:00
committed by GitHub
parent 55a9f9a7f1
commit d2c11ca50b
8 changed files with 312 additions and 109 deletions
+173 -30
View File
@@ -4,7 +4,7 @@ import {BACKSPACE, DELETE, ENTER, LEFT_ARROW, RIGHT_ARROW, SPACE, TAB} from '@an
import {createKeyboardEvent, dispatchFakeEvent, dispatchKeyboardEvent} from '@angular/cdk/testing';
import {Component, DebugElement, QueryList, ViewChild, ViewChildren} from '@angular/core';
import {async, ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing';
import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms';
import {FormControl, FormsModule, NgForm, ReactiveFormsModule, Validators} from '@angular/forms';
import {MatFormFieldModule} from '@angular/material/form-field';
import {By} from '@angular/platform-browser';
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
@@ -35,6 +35,7 @@ describe('MatChipList', () => {
NoopAnimationsModule
],
declarations: [
ChipListWithFormErrorMessages,
StandardChipList,
FormFieldChipList,
BasicChipList,
@@ -864,6 +865,121 @@ describe('MatChipList', () => {
});
});
describe('error messages', () => {
let errorTestComponent: ChipListWithFormErrorMessages;
let containerEl: HTMLElement;
let chipListEl: HTMLElement;
beforeEach(() => {
fixture = TestBed.createComponent(ChipListWithFormErrorMessages);
fixture.detectChanges();
errorTestComponent = fixture.componentInstance;
containerEl = fixture.debugElement.query(By.css('mat-form-field')).nativeElement;
chipListEl = fixture.debugElement.query(By.css('mat-chip-list')).nativeElement;
});
it('should not show any errors if the user has not interacted', () => {
expect(errorTestComponent.formControl.untouched)
.toBe(true, 'Expected untouched form control');
expect(containerEl.querySelectorAll('mat-error').length).toBe(0, 'Expected no error message');
expect(chipListEl.getAttribute('aria-invalid'))
.toBe('false', 'Expected aria-invalid to be set to "false".');
});
it('should display an error message when the chip list is touched and invalid', async(() => {
expect(errorTestComponent.formControl.invalid)
.toBe(true, 'Expected form control to be invalid');
expect(containerEl.querySelectorAll('mat-error').length)
.toBe(0, 'Expected no error message');
errorTestComponent.formControl.markAsTouched();
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(containerEl.classList)
.toContain('mat-form-field-invalid', 'Expected container to have the invalid CSS class.');
expect(containerEl.querySelectorAll('mat-error').length)
.toBe(1, 'Expected one error message to have been rendered.');
expect(chipListEl.getAttribute('aria-invalid'))
.toBe('true', 'Expected aria-invalid to be set to "true".');
});
}));
it('should display an error message when the parent form is submitted', fakeAsync(() => {
expect(errorTestComponent.form.submitted)
.toBe(false, 'Expected form not to have been submitted');
expect(errorTestComponent.formControl.invalid)
.toBe(true, 'Expected form control to be invalid');
expect(containerEl.querySelectorAll('mat-error').length).toBe(0, 'Expected no error message');
dispatchFakeEvent(fixture.debugElement.query(By.css('form')).nativeElement, 'submit');
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(errorTestComponent.form.submitted)
.toBe(true, 'Expected form to have been submitted');
expect(containerEl.classList)
.toContain('mat-form-field-invalid', 'Expected container to have the invalid CSS class.');
expect(containerEl.querySelectorAll('mat-error').length)
.toBe(1, 'Expected one error message to have been rendered.');
expect(chipListEl.getAttribute('aria-invalid'))
.toBe('true', 'Expected aria-invalid to be set to "true".');
});
}));
it('should hide the errors and show the hints once the chip list becomes valid',
fakeAsync(() => {
errorTestComponent.formControl.markAsTouched();
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(containerEl.classList)
.toContain('mat-form-field-invalid', 'Expected container to have the invalid CSS class.');
expect(containerEl.querySelectorAll('mat-error').length)
.toBe(1, 'Expected one error message to have been rendered.');
expect(containerEl.querySelectorAll('mat-hint').length)
.toBe(0, 'Expected no hints to be shown.');
errorTestComponent.formControl.setValue('something');
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(containerEl.classList).not.toContain('mat-form-field-invalid',
'Expected container not to have the invalid class when valid.');
expect(containerEl.querySelectorAll('mat-error').length)
.toBe(0, 'Expected no error messages when the input is valid.');
expect(containerEl.querySelectorAll('mat-hint').length)
.toBe(1, 'Expected one hint to be shown once the input is valid.');
});
});
}));
it('should set the proper role on the error messages', () => {
errorTestComponent.formControl.markAsTouched();
fixture.detectChanges();
expect(containerEl.querySelector('mat-error')!.getAttribute('role')).toBe('alert');
});
it('sets the aria-describedby to reference errors when in error state', () => {
let hintId = fixture.debugElement.query(By.css('.mat-hint')).nativeElement.getAttribute('id');
let describedBy = chipListEl.getAttribute('aria-describedby');
expect(hintId).toBeTruthy('hint should be shown');
expect(describedBy).toBe(hintId);
fixture.componentInstance.formControl.markAsTouched();
fixture.detectChanges();
let errorIds = fixture.debugElement.queryAll(By.css('.mat-error'))
.map(el => el.nativeElement.getAttribute('id')).join(' ');
describedBy = chipListEl.getAttribute('aria-describedby');
expect(errorIds).toBeTruthy('errors should be shown');
expect(describedBy).toBe(errorIds);
});
});
function setupStandardList() {
fixture = TestBed.createComponent(StandardChipList);
fixture.detectChanges();
@@ -940,14 +1056,14 @@ class FormFieldChipList {
})
class BasicChipList {
foods: any[] = [
{ value: 'steak-0', viewValue: 'Steak' },
{ value: 'pizza-1', viewValue: 'Pizza' },
{ value: 'tacos-2', viewValue: 'Tacos', disabled: true },
{ value: 'sandwich-3', viewValue: 'Sandwich' },
{ value: 'chips-4', viewValue: 'Chips' },
{ value: 'eggs-5', viewValue: 'Eggs' },
{ value: 'pasta-6', viewValue: 'Pasta' },
{ value: 'sushi-7', viewValue: 'Sushi' },
{value: 'steak-0', viewValue: 'Steak'},
{value: 'pizza-1', viewValue: 'Pizza'},
{value: 'tacos-2', viewValue: 'Tacos', disabled: true},
{value: 'sandwich-3', viewValue: 'Sandwich'},
{value: 'chips-4', viewValue: 'Chips'},
{value: 'eggs-5', viewValue: 'Eggs'},
{value: 'pasta-6', viewValue: 'Pasta'},
{value: 'sushi-7', viewValue: 'Sushi'},
];
control = new FormControl();
isRequired: boolean;
@@ -975,14 +1091,14 @@ class BasicChipList {
})
class MultiSelectionChipList {
foods: any[] = [
{ value: 'steak-0', viewValue: 'Steak' },
{ value: 'pizza-1', viewValue: 'Pizza' },
{ value: 'tacos-2', viewValue: 'Tacos', disabled: true },
{ value: 'sandwich-3', viewValue: 'Sandwich' },
{ value: 'chips-4', viewValue: 'Chips' },
{ value: 'eggs-5', viewValue: 'Eggs' },
{ value: 'pasta-6', viewValue: 'Pasta' },
{ value: 'sushi-7', viewValue: 'Sushi' },
{value: 'steak-0', viewValue: 'Steak'},
{value: 'pizza-1', viewValue: 'Pizza'},
{value: 'tacos-2', viewValue: 'Tacos', disabled: true},
{value: 'sandwich-3', viewValue: 'Sandwich'},
{value: 'chips-4', viewValue: 'Chips'},
{value: 'eggs-5', viewValue: 'Eggs'},
{value: 'pasta-6', viewValue: 'Pasta'},
{value: 'sushi-7', viewValue: 'Sushi'},
];
control = new FormControl();
isRequired: boolean;
@@ -1013,14 +1129,14 @@ class MultiSelectionChipList {
})
class InputChipList {
foods: any[] = [
{ value: 'steak-0', viewValue: 'Steak' },
{ value: 'pizza-1', viewValue: 'Pizza' },
{ value: 'tacos-2', viewValue: 'Tacos', disabled: true },
{ value: 'sandwich-3', viewValue: 'Sandwich' },
{ value: 'chips-4', viewValue: 'Chips' },
{ value: 'eggs-5', viewValue: 'Eggs' },
{ value: 'pasta-6', viewValue: 'Pasta' },
{ value: 'sushi-7', viewValue: 'Sushi' },
{value: 'steak-0', viewValue: 'Steak'},
{value: 'pizza-1', viewValue: 'Pizza'},
{value: 'tacos-2', viewValue: 'Tacos', disabled: true},
{value: 'sandwich-3', viewValue: 'Sandwich'},
{value: 'chips-4', viewValue: 'Chips'},
{value: 'eggs-5', viewValue: 'Eggs'},
{value: 'pasta-6', viewValue: 'Pasta'},
{value: 'sushi-7', viewValue: 'Sushi'},
];
control = new FormControl();
@@ -1061,8 +1177,8 @@ class InputChipList {
})
class FalsyValueChipList {
foods: any[] = [
{ value: 0, viewValue: 'Steak' },
{ value: 1, viewValue: 'Pizza' },
{value: 0, viewValue: 'Steak'},
{value: 1, viewValue: 'Pizza'},
];
control = new FormControl();
@ViewChildren(MatChip) chips: QueryList<MatChip>;
@@ -1079,9 +1195,36 @@ class FalsyValueChipList {
})
class SelectedChipList {
foods: any[] = [
{ value: 0, viewValue: 'Steak', selected: true },
{ value: 1, viewValue: 'Pizza', selected: false },
{ value: 2, viewValue: 'Pasta', selected: true },
{value: 0, viewValue: 'Steak', selected: true},
{value: 1, viewValue: 'Pizza', selected: false},
{value: 2, viewValue: 'Pasta', selected: true},
];
@ViewChildren(MatChip) chips: QueryList<MatChip>;
}
@Component({
template: `
<form #form="ngForm" novalidate>
<mat-form-field>
<mat-chip-list [formControl]="formControl">
<mat-chip *ngFor="let food of foods" [value]="food.value" [selected]="food.selected">
{{food.viewValue}}
</mat-chip>
</mat-chip-list>
<mat-hint>Please select a chip, or type to add a new chip</mat-hint>
<mat-error>Should have value</mat-error>
</mat-form-field>
</form>
`
})
class ChipListWithFormErrorMessages {
foods: any[] = [
{value: 0, viewValue: 'Steak', selected: true},
{value: 1, viewValue: 'Pizza', selected: false},
{value: 2, viewValue: 'Pasta', selected: true},
];
@ViewChildren(MatChip) chips: QueryList<MatChip>;
@ViewChild('form') form: NgForm;
formControl = new FormControl('', Validators.required);
}
+37 -20
View File
@@ -18,6 +18,7 @@ import {
ChangeDetectorRef,
Component,
ContentChildren,
DoCheck,
ElementRef,
EventEmitter,
Input,
@@ -29,15 +30,31 @@ import {
Self,
ViewEncapsulation,
} from '@angular/core';
import {ControlValueAccessor, FormGroupDirective, NgControl, NgForm} from '@angular/forms';
import {
ControlValueAccessor,
FormGroupDirective,
NgControl,
NgForm
} from '@angular/forms';
import {ErrorStateMatcher, mixinErrorState, CanUpdateErrorState} from '@angular/material/core';
import {MatFormFieldControl} from '@angular/material/form-field';
import {Observable} from 'rxjs/Observable';
import {merge} from 'rxjs/observable/merge';
import {Subject} from 'rxjs/Subject';
import {Subscription} from 'rxjs/Subscription';
import {MatChip, MatChipEvent, MatChipSelectionChange} from './chip';
import {MatChipInput} from './chip-input';
// Boilerplate for applying mixins to MatChipList.
/** @docs-private */
export class MatChipListBase {
constructor(public _defaultErrorStateMatcher: ErrorStateMatcher,
public _parentForm: NgForm,
public _parentFormGroup: FormGroupDirective,
public ngControl: NgControl) {}
}
export const _MatChipListMixinBase = mixinErrorState(MatChipListBase);
// Increasing integer for generating unique ids for chip-list components.
let nextUniqueId = 0;
@@ -78,16 +95,10 @@ export class MatChipListChange {
preserveWhitespaces: false,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class MatChipList implements MatFormFieldControl<any>, ControlValueAccessor,
AfterContentInit, OnInit, OnDestroy {
export class MatChipList extends _MatChipListMixinBase implements MatFormFieldControl<any>,
ControlValueAccessor, AfterContentInit, DoCheck, OnInit, OnDestroy, CanUpdateErrorState {
readonly controlType = 'mat-chip-list';
/**
* Stream that emits whenever the state of the input changes such that the wrapping `MatFormField`
* needs to run change detection.
*/
stateChanges = new Subject<void>();
/** When a chip is destroyed, we track the index so we can focus the appropriate next chip. */
protected _lastDestroyedIndex: number|null = null;
@@ -173,6 +184,9 @@ export class MatChipList implements MatFormFieldControl<any>, ControlValueAccess
return this.empty ? null : 'listbox';
}
/** An object used to control when error messages are shown. */
@Input() errorStateMatcher: ErrorStateMatcher;
/** Whether the user should be allowed to select multiple chips. */
@Input()
get multiple(): boolean { return this._multiple; }
@@ -251,14 +265,6 @@ export class MatChipList implements MatFormFieldControl<any>, ControlValueAccess
get disabled() { return this.ngControl ? this.ngControl.disabled : this._disabled; }
set disabled(value: any) { this._disabled = coerceBooleanProperty(value); }
/** Whether the chip list is in an error state. */
get errorState(): boolean {
const isInvalid = this.ngControl && this.ngControl.invalid;
const isTouched = this.ngControl && this.ngControl.touched;
const isSubmitted = (this._parentFormGroup && this._parentFormGroup.submitted) ||
(this._parentForm && this._parentForm.submitted);
return !!(isInvalid && (isTouched || isSubmitted));
}
/** Orientation of the chip list. */
@Input('aria-orientation') ariaOrientation: 'horizontal' | 'vertical' = 'horizontal';
@@ -313,9 +319,11 @@ export class MatChipList implements MatFormFieldControl<any>, ControlValueAccess
constructor(protected _elementRef: ElementRef,
private _changeDetectorRef: ChangeDetectorRef,
@Optional() private _dir: Directionality,
@Optional() private _parentForm: NgForm,
@Optional() private _parentFormGroup: FormGroupDirective,
@Optional() _parentForm: NgForm,
@Optional() _parentFormGroup: FormGroupDirective,
_defaultErrorStateMatcher: ErrorStateMatcher,
@Optional() @Self() public ngControl: NgControl) {
super(_defaultErrorStateMatcher, _parentForm, _parentFormGroup, ngControl);
if (this.ngControl) {
this.ngControl.valueAccessor = this;
}
@@ -352,6 +360,15 @@ export class MatChipList implements MatFormFieldControl<any>, ControlValueAccess
this.stateChanges.next();
}
ngDoCheck() {
if (this.ngControl) {
// We need to re-evaluate this on every change detection cycle, because there are some
// error triggers that we can't subscribe to (e.g. parent form submissions). This means
// that whatever logic is in here has to be super lean or we risk destroying the performance.
this.updateErrorState();
}
}
ngOnDestroy(): void {
this._tabOutSubscription.unsubscribe();
+3 -1
View File
@@ -7,6 +7,7 @@
*/
import {NgModule} from '@angular/core';
import {ErrorStateMatcher} from '@angular/material/core';
import {MatChipList} from './chip-list';
import {MatBasicChip, MatChip, MatChipRemove} from './chip';
import {MatChipInput} from './chip-input';
@@ -15,6 +16,7 @@ import {MatChipInput} from './chip-input';
@NgModule({
imports: [],
exports: [MatChipList, MatChip, MatChipInput, MatChipRemove, MatChipRemove, MatBasicChip],
declarations: [MatChipList, MatChip, MatChipInput, MatChipRemove, MatChipRemove, MatBasicChip]
declarations: [MatChipList, MatChip, MatChipInput, MatChipRemove, MatChipRemove, MatBasicChip],
providers: [ErrorStateMatcher]
})
export class MatChipsModule {}
@@ -0,0 +1,65 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Constructor} from './constructor';
import {ErrorStateMatcher} from '../error/error-options';
import {FormControl, FormGroupDirective, NgControl, NgForm} from '@angular/forms';
import {Subject} from 'rxjs/Subject';
/** @docs-private */
export interface CanUpdateErrorState {
updateErrorState();
stateChanges: Subject<void>;
errorState: boolean;
errorStateMatcher: ErrorStateMatcher;
}
/** @docs-private */
export interface HasErrorState {
_parentFormGroup: FormGroupDirective;
_parentForm: NgForm;
_defaultErrorStateMatcher: ErrorStateMatcher;
ngControl: NgControl;
}
/**
* Mixin to augment a directive with updateErrorState method.
* For component with `errorState` and need to update `errorState`.
*/
export function mixinErrorState<T extends Constructor<HasErrorState>>(base: T)
: Constructor<CanUpdateErrorState> & T {
return class extends base {
/** Whether the component is in an error state. */
errorState: boolean = false;
/**
* Stream that emits whenever the state of the input changes such that the wrapping
* `MatFormField needs to run change detection.
*/
stateChanges = new Subject<void>();
errorStateMatcher: ErrorStateMatcher;
updateErrorState() {
const oldState = this.errorState;
const parent = this._parentFormGroup || this._parentForm;
const matcher = this.errorStateMatcher || this._defaultErrorStateMatcher;
const control = this.ngControl ? this.ngControl.control as FormControl : null;
const newState = matcher.isErrorState(control, parent);
if (newState !== oldState) {
this.errorState = newState;
this.stateChanges.next();
}
}
constructor(...args: any[]) {
super(...args);
}
};
}
+1
View File
@@ -11,3 +11,4 @@ export {CanDisable, mixinDisabled} from './disabled';
export {CanColor, mixinColor, ThemePalette} from './color';
export {CanDisableRipple, mixinDisableRipple} from './disable-ripple';
export {HasTabIndex, mixinTabIndex} from './tabindex';
export {CanUpdateErrorState, mixinErrorState} from './error-state';
@@ -31,4 +31,3 @@ export function mixinTabIndex<T extends Constructor<CanDisable>>(base: T, defaul
}
};
}
+18 -24
View File
@@ -19,8 +19,8 @@ import {
Optional,
Self,
} from '@angular/core';
import {FormControl, FormGroupDirective, NgControl, NgForm} from '@angular/forms';
import {ErrorStateMatcher} from '@angular/material/core';
import {FormGroupDirective, NgControl, NgForm} from '@angular/forms';
import {ErrorStateMatcher, mixinErrorState, CanUpdateErrorState} from '@angular/material/core';
import {MatFormFieldControl} from '@angular/material/form-field';
import {Subject} from 'rxjs/Subject';
import {getMatInputUnsupportedTypeError} from './input-errors';
@@ -43,6 +43,15 @@ const MAT_INPUT_INVALID_TYPES = [
let nextUniqueId = 0;
// Boilerplate for applying mixins to MatInput.
/** @docs-private */
export class MatInputBase {
constructor(public _defaultErrorStateMatcher: ErrorStateMatcher,
public _parentForm: NgForm,
public _parentFormGroup: FormGroupDirective,
public ngControl: NgControl) {}
}
export const _MatInputMixinBase = mixinErrorState(MatInputBase);
/** Directive that allows a native input to work inside a `MatFormField`. */
@Directive({
@@ -67,7 +76,8 @@ let nextUniqueId = 0;
},
providers: [{provide: MatFormFieldControl, useExisting: MatInput}],
})
export class MatInput implements MatFormFieldControl<any>, OnChanges, OnDestroy, DoCheck {
export class MatInput extends _MatInputMixinBase implements MatFormFieldControl<any>, OnChanges,
OnDestroy, DoCheck, CanUpdateErrorState {
/** Variables used as cache for getters and setters. */
protected _type = 'text';
protected _disabled = false;
@@ -81,9 +91,6 @@ export class MatInput implements MatFormFieldControl<any>, OnChanges, OnDestroy,
/** Whether the input is focused. */
focused = false;
/** Whether the input is in an error state. */
errorState = false;
/** The aria-describedby attribute on the input for improved a11y. */
_ariaDescribedby: string;
@@ -162,10 +169,11 @@ export class MatInput implements MatFormFieldControl<any>, OnChanges, OnDestroy,
constructor(protected _elementRef: ElementRef,
protected _platform: Platform,
@Optional() @Self() public ngControl: NgControl,
@Optional() protected _parentForm: NgForm,
@Optional() protected _parentFormGroup: FormGroupDirective,
private _defaultErrorStateMatcher: ErrorStateMatcher,
@Optional() _parentForm: NgForm,
@Optional() _parentFormGroup: FormGroupDirective,
_defaultErrorStateMatcher: ErrorStateMatcher,
@Optional() @Self() @Inject(MAT_INPUT_VALUE_ACCESSOR) inputValueAccessor: any) {
super(_defaultErrorStateMatcher, _parentForm, _parentFormGroup, ngControl);
// If no input value accessor was explicitly specified, use the element as the input value
// accessor.
this._inputValueAccessor = inputValueAccessor || this._elementRef.nativeElement;
@@ -207,7 +215,7 @@ export class MatInput implements MatFormFieldControl<any>, OnChanges, OnDestroy,
// We need to re-evaluate this on every change detection cycle, because there are some
// error triggers that we can't subscribe to (e.g. parent form submissions). This means
// that whatever logic is in here has to be super lean or we risk destroying the performance.
this._updateErrorState();
this.updateErrorState();
} else {
// When the input isn't used together with `@angular/forms`, we need to check manually for
// changes to the native `value` property in order to update the floating label.
@@ -235,20 +243,6 @@ export class MatInput implements MatFormFieldControl<any>, OnChanges, OnDestroy,
// FormsModule or ReactiveFormsModule, because Angular forms also listens to input events.
}
/** Re-evaluates the error state. This is only relevant with @angular/forms. */
protected _updateErrorState() {
const oldState = this.errorState;
const parent = this._parentFormGroup || this._parentForm;
const matcher = this.errorStateMatcher || this._defaultErrorStateMatcher;
const control = this.ngControl ? this.ngControl.control as FormControl : null;
const newState = matcher.isErrorState(control, parent);
if (newState !== oldState) {
this.errorState = newState;
this.stateChanges.next();
}
}
/** Does some manual dirty checking on the native input `value` property. */
protected _dirtyCheckNativeValue() {
const newValue = this.value;
+15 -33
View File
@@ -53,7 +53,6 @@ import {
} from '@angular/core';
import {
ControlValueAccessor,
FormControl,
FormGroupDirective,
NgControl,
NgForm
@@ -61,6 +60,8 @@ import {
import {
CanDisable,
ErrorStateMatcher,
CanUpdateErrorState,
mixinErrorState,
HasTabIndex,
MatOptgroup,
MatOption,
@@ -142,9 +143,13 @@ export class MatSelectChange {
// Boilerplate for applying mixins to MatSelect.
/** @docs-private */
export class MatSelectBase {
constructor(public _elementRef: ElementRef) {}
constructor(public _elementRef: ElementRef,
public _defaultErrorStateMatcher: ErrorStateMatcher,
public _parentForm: NgForm,
public _parentFormGroup: FormGroupDirective,
public ngControl: NgControl) {}
}
export const _MatSelectMixinBase = mixinTabIndex(mixinDisabled(MatSelectBase));
export const _MatSelectMixinBase = mixinTabIndex(mixinDisabled(mixinErrorState(MatSelectBase)));
/**
@@ -198,7 +203,7 @@ export class MatSelectTrigger {}
})
export class MatSelect extends _MatSelectMixinBase implements AfterContentInit, OnChanges,
OnDestroy, OnInit, DoCheck, ControlValueAccessor, CanDisable, HasTabIndex,
MatFormFieldControl<any> {
MatFormFieldControl<any>, CanUpdateErrorState {
/** Whether or not the overlay panel is open. */
private _panelOpen = false;
@@ -284,12 +289,6 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
},
];
/**
* Stream that emits whenever the state of the select changes such that the wrapping
* `MatFormField` needs to run change detection.
*/
stateChanges = new Subject<void>();
/** Whether the select is focused. */
focused = false;
@@ -451,17 +450,17 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
private _viewportRuler: ViewportRuler,
private _changeDetectorRef: ChangeDetectorRef,
private _ngZone: NgZone,
private _defaultErrorStateMatcher: ErrorStateMatcher,
_defaultErrorStateMatcher: ErrorStateMatcher,
elementRef: ElementRef,
@Optional() private _dir: Directionality,
@Optional() private _parentForm: NgForm,
@Optional() private _parentFormGroup: FormGroupDirective,
@Optional() _parentForm: NgForm,
@Optional() _parentFormGroup: FormGroupDirective,
@Optional() private _parentFormField: MatFormField,
@Self() @Optional() public ngControl: NgControl,
@Attribute('tabindex') tabIndex: string,
@Inject(MAT_SELECT_SCROLL_STRATEGY) private _scrollStrategyFactory) {
super(elementRef);
super(elementRef, _defaultErrorStateMatcher, _parentForm,
_parentFormGroup, ngControl);
if (this.ngControl) {
this.ngControl.valueAccessor = this;
@@ -489,7 +488,7 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
ngDoCheck() {
if (this.ngControl) {
this._updateErrorState();
this.updateErrorState();
}
}
@@ -729,9 +728,6 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
return !this._selectionModel || this._selectionModel.isEmpty();
}
/** Whether the select is in an error state. */
errorState: boolean;
private _initializeSelection(): void {
// Defer setting the value in order to avoid the "Expression
// has changed after it was checked" errors from Angular.
@@ -1188,20 +1184,6 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
return this._triggerFontSize * SELECT_ITEM_HEIGHT_EM;
}
/** Updates the select's error state. Only relevant when used with @angular/forms. */
private _updateErrorState() {
const oldState = this.errorState;
const parent = this._parentFormGroup || this._parentForm;
const matcher = this.errorStateMatcher || this._defaultErrorStateMatcher;
const control = this.ngControl ? this.ngControl.control as FormControl : null;
const newState = matcher.isErrorState(control, parent);
if (newState !== oldState) {
this.errorState = newState;
this.stateChanges.next();
}
}
// Implemented as part of MatFormFieldControl.
setDescribedByIds(ids: string[]) {
this._ariaDescribedby = ids.join(' ');