From e4e7ee90f80b09e2f39cb90ad25b56c9fdc443af Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Thu, 3 Aug 2017 00:57:32 +0200 Subject: [PATCH] fix(autocomplete): placeholder not resetting properly (#6141) Fixes the following regressions that were introduced by the switch to OnPush change detection: * The floating placeholder not resetting when the user closes the panel without selecting a value. * The placeholder overlapping the input value when the autocomplete has a preselected value and `floatPlaceholder="never"`. --- src/lib/autocomplete/autocomplete-trigger.ts | 11 +++++- src/lib/autocomplete/autocomplete.spec.ts | 36 ++++++++++++++++++-- src/lib/input/input-container.spec.ts | 19 +++++++++++ src/lib/input/input-container.ts | 12 +++++-- 4 files changed, 72 insertions(+), 6 deletions(-) diff --git a/src/lib/autocomplete/autocomplete-trigger.ts b/src/lib/autocomplete/autocomplete-trigger.ts index cdc00c36aeba..7710bd335033 100644 --- a/src/lib/autocomplete/autocomplete-trigger.ts +++ b/src/lib/autocomplete/autocomplete-trigger.ts @@ -409,9 +409,18 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy { private _setTriggerValue(value: any): void { const toDisplay = this.autocomplete.displayWith ? this.autocomplete.displayWith(value) : value; + // Simply falling back to an empty string if the display value is falsy does not work properly. // The display value can also be the number zero and shouldn't fall back to an empty string. - this._element.nativeElement.value = toDisplay != null ? toDisplay : ''; + const inputValue = toDisplay != null ? toDisplay : ''; + + // If it's used in a Material container, we should set it through + // the property so it can go through the change detection. + if (this._inputContainer) { + this._inputContainer._mdInputChild.value = inputValue; + } else { + this._element.nativeElement.value = inputValue; + } } /** diff --git a/src/lib/autocomplete/autocomplete.spec.ts b/src/lib/autocomplete/autocomplete.spec.ts index d043c13ee17f..495af40ba1a9 100644 --- a/src/lib/autocomplete/autocomplete.spec.ts +++ b/src/lib/autocomplete/autocomplete.spec.ts @@ -54,7 +54,8 @@ describe('MdAutocomplete', () => { AutocompleteWithNumbers, AutocompleteWithOnPushDelay, AutocompleteWithNativeInput, - AutocompleteWithoutPanel + AutocompleteWithoutPanel, + AutocompleteWithFormsAndNonfloatingPlaceholder ], providers: [ {provide: OverlayContainer, useFactory: () => { @@ -1314,6 +1315,21 @@ describe('MdAutocomplete', () => { }).toThrow(getMdAutocompleteMissingPanelError()); })); + it('should hide the placeholder with a preselected form control value ' + + 'and a disabled floating placeholder', fakeAsync(() => { + const fixture = TestBed.createComponent(AutocompleteWithFormsAndNonfloatingPlaceholder); + + fixture.detectChanges(); + tick(); + fixture.detectChanges(); + + const input = fixture.nativeElement.querySelector('input'); + const placeholder = fixture.nativeElement.querySelector('.mat-input-placeholder'); + + expect(input.value).toBe('California'); + expect(placeholder.classList).not.toContain('mat-empty'); + })); + }); it('should have correct width when opened', () => { @@ -1501,7 +1517,6 @@ class AutocompleteWithoutForms { onInput(value: any) { this.filteredStates = this.states.filter(s => new RegExp(value, 'gi').test(s)); } - } @@ -1531,7 +1546,6 @@ class AutocompleteWithNgModel { onInput(value: any) { this.filteredStates = this.states.filter(s => new RegExp(value, 'gi').test(s)); } - } @Component({ @@ -1611,3 +1625,19 @@ class AutocompleteWithNativeInput { class AutocompleteWithoutPanel { @ViewChild(MdAutocompleteTrigger) trigger: MdAutocompleteTrigger; } + + +@Component({ + template: ` + + + + + + California + + ` +}) +class AutocompleteWithFormsAndNonfloatingPlaceholder { + formControl = new FormControl('California'); +} diff --git a/src/lib/input/input-container.spec.ts b/src/lib/input/input-container.spec.ts index cb31144b2670..58cd7889fbc4 100644 --- a/src/lib/input/input-container.spec.ts +++ b/src/lib/input/input-container.spec.ts @@ -566,6 +566,25 @@ describe('MdInputContainer without forms', function () { expect(labelEl.classList).not.toContain('mat-float'); }); + it('should be able to toggle the floating placeholder programmatically', () => { + const fixture = TestBed.createComponent(MdInputContainerWithId); + + fixture.detectChanges(); + + const inputContainer = fixture.debugElement.query(By.directive(MdInputContainer)); + const containerInstance = inputContainer.componentInstance as MdInputContainer; + const placeholder = inputContainer.nativeElement.querySelector('.mat-input-placeholder'); + + expect(containerInstance.floatPlaceholder).toBe('auto'); + expect(placeholder.classList).toContain('mat-empty', 'Expected input to be considered empty.'); + + containerInstance.floatPlaceholder = 'always'; + fixture.detectChanges(); + + expect(placeholder.classList) + .not.toContain('mat-empty', 'Expected input to be considered not empty.'); + }); + it('should not have prefix and suffix elements when none are specified', () => { let fixture = TestBed.createComponent(MdInputContainerWithId); fixture.detectChanges(); diff --git a/src/lib/input/input-container.ts b/src/lib/input/input-container.ts index 89fdd4e2f573..e186f62eaeb4 100644 --- a/src/lib/input/input-container.ts +++ b/src/lib/input/input-container.ts @@ -207,7 +207,12 @@ export class MdInputDirective implements OnChanges, OnDestroy, DoCheck { /** The input element's value. */ get value() { return this._elementRef.nativeElement.value; } - set value(value: string) { this._elementRef.nativeElement.value = value; } + set value(value: string) { + if (value !== this.value) { + this._elementRef.nativeElement.value = value; + this._stateChanges.next(); + } + } /** Whether the input is empty. */ get empty() { @@ -443,7 +448,10 @@ export class MdInputContainer implements AfterViewInit, AfterContentInit, AfterC @Input() get floatPlaceholder() { return this._floatPlaceholder; } set floatPlaceholder(value: FloatPlaceholderType) { - this._floatPlaceholder = value || this._placeholderOptions.float || 'auto'; + if (value !== this._floatPlaceholder) { + this._floatPlaceholder = value || this._placeholderOptions.float || 'auto'; + this._changeDetectorRef.markForCheck(); + } } private _floatPlaceholder: FloatPlaceholderType;