Skip to content

Commit 904a5ea

Browse files
thomaspinkmmalerba
authored andcommittedDec 4, 2018
fix(autocomplete): update template when changing autocomplete in trigger (#13814)
* fix(autocomplete): update template when changing autocomplete in trigger * fix(autocomplet): improved test for changing autocomplete panel fix(autocomplete): removed debug command in test * fix(autocomplete): moved portal from autocomplete-trigger to autocomplete * fix(autocomplete): setting currentPortal in trigger * fix(autocomplete): fixes issue when detaching when panel updates * feat(autocomplete): detaching portal when autocomplete input changes * feat(autocomplete): moved overlay detach to its own method * feat(autocomplete): typo & removed yarn-error.log
1 parent a0bcd81 commit 904a5ea

File tree

3 files changed

+80
-15
lines changed

3 files changed

+80
-15
lines changed
 

‎src/lib/autocomplete/autocomplete-trigger.ts

+21-14
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import {
1515
PositionStrategy,
1616
ScrollStrategy,
1717
} from '@angular/cdk/overlay';
18-
import {TemplatePortal} from '@angular/cdk/portal';
1918
import {DOCUMENT} from '@angular/common';
2019
import {filter, take, switchMap, delay, tap, map} from 'rxjs/operators';
2120
import {
@@ -30,7 +29,6 @@ import {
3029
NgZone,
3130
OnDestroy,
3231
Optional,
33-
ViewContainerRef,
3432
} from '@angular/core';
3533
import {ViewportRuler} from '@angular/cdk/scrolling';
3634
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
@@ -117,9 +115,9 @@ export function getMatAutocompleteMissingPanelError(): Error {
117115
})
118116
export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
119117
private _overlayRef: OverlayRef | null;
120-
private _portal: TemplatePortal;
121118
private _componentDestroyed = false;
122119
private _autocompleteDisabled = false;
120+
private _autocomplete: MatAutocomplete;
123121
private _scrollStrategy: () => ScrollStrategy;
124122

125123
/** Old value of the native input. Used to work around issues with the `input` event on IE. */
@@ -132,7 +130,7 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
132130
private _manuallyFloatingLabel = false;
133131

134132
/** The subscription for closing actions (some are bound to document). */
135-
private _closingActionsSubscription: Subscription;
133+
private _closingActionsSubscription = Subscription.EMPTY;
136134

137135
/** Subscription to viewport size changes. */
138136
private _viewportSubscription = Subscription.EMPTY;
@@ -166,7 +164,12 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
166164
_onTouched = () => {};
167165

168166
/** The autocomplete panel to be attached to this trigger. */
169-
@Input('matAutocomplete') autocomplete: MatAutocomplete;
167+
@Input('matAutocomplete')
168+
get autocomplete(): MatAutocomplete { return this._autocomplete; }
169+
set autocomplete(value: MatAutocomplete) {
170+
this._autocomplete = value;
171+
this._detachOverlay();
172+
}
170173

171174
/**
172175
* Reference relative to which to position the autocomplete panel.
@@ -190,8 +193,8 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
190193
this._autocompleteDisabled = coerceBooleanProperty(value);
191194
}
192195

193-
constructor(private _element: ElementRef<HTMLInputElement>, private _overlay: Overlay,
194-
private _viewContainerRef: ViewContainerRef,
196+
constructor(private _element: ElementRef<HTMLInputElement>,
197+
private _overlay: Overlay,
195198
private _zone: NgZone,
196199
private _changeDetectorRef: ChangeDetectorRef,
197200
@Inject(MAT_AUTOCOMPLETE_SCROLL_STRATEGY) scrollStrategy: any,
@@ -246,12 +249,9 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
246249
this.autocomplete.closed.emit();
247250
}
248251

249-
this.autocomplete._isOpen = this._overlayAttached = false;
252+
this.autocomplete._isOpen = false;
253+
this._detachOverlay();
250254

251-
if (this._overlayRef && this._overlayRef.hasAttached()) {
252-
this._overlayRef.detach();
253-
this._closingActionsSubscription.unsubscribe();
254-
}
255255

256256
// Note that in some cases this can end up being called after the component is destroyed.
257257
// Add a check to ensure that we don't try to run change detection on a destroyed view.
@@ -570,7 +570,6 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
570570
}
571571

572572
if (!this._overlayRef) {
573-
this._portal = new TemplatePortal(this.autocomplete.template, this._viewContainerRef);
574573
this._overlayRef = this._overlay.create(this._getOverlayConfig());
575574

576575
// Use the `keydownEvents` in order to take advantage of
@@ -597,7 +596,7 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
597596
}
598597

599598
if (this._overlayRef && !this._overlayRef.hasAttached()) {
600-
this._overlayRef.attach(this._portal);
599+
this._overlayRef.attach(this.autocomplete._portal);
601600
this._closingActionsSubscription = this._subscribeToClosingActions();
602601
}
603602

@@ -613,6 +612,14 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
613612
}
614613
}
615614

615+
private _detachOverlay() {
616+
this._overlayAttached = false;
617+
this._closingActionsSubscription.unsubscribe();
618+
if (this._overlayRef) {
619+
this._overlayRef.detach();
620+
}
621+
}
622+
616623
private _getOverlayConfig(): OverlayConfig {
617624
return new OverlayConfig({
618625
positionStrategy: this._getOverlayPosition(),

‎src/lib/autocomplete/autocomplete.spec.ts

+47
Original file line numberDiff line numberDiff line change
@@ -2233,6 +2233,35 @@ describe('MatAutocomplete', () => {
22332233
expect(formControl.value).toBe('Cal', 'Expected new value to be propagated to model');
22342234
}));
22352235

2236+
it('should work when dynamically changing the autocomplete', () => {
2237+
const fixture = createComponent(DynamicallyChangingAutocomplete);
2238+
fixture.detectChanges();
2239+
const input = fixture.debugElement.query(By.css('input')).nativeElement;
2240+
2241+
dispatchFakeEvent(input, 'focusin');
2242+
fixture.detectChanges();
2243+
2244+
expect(overlayContainerElement.textContent).toContain('First',
2245+
`Expected panel to display the option of the first autocomplete.`);
2246+
expect(overlayContainerElement.textContent).not.toContain('Second',
2247+
`Expected panel to not display the option of the second autocomplete.`);
2248+
2249+
dispatchFakeEvent(document, 'click');
2250+
fixture.detectChanges();
2251+
2252+
fixture.componentInstance.trigger.autocomplete = fixture.componentInstance.autoTow;
2253+
fixture.detectChanges();
2254+
2255+
dispatchFakeEvent(input, 'focusin');
2256+
fixture.detectChanges();
2257+
2258+
expect(overlayContainerElement.textContent).not.toContain('First',
2259+
`Expected panel to not display the option of the first autocomplete.`);
2260+
expect(overlayContainerElement.textContent).toContain('Second',
2261+
`Expected panel to display the option of the second autocomplete.`);
2262+
2263+
});
2264+
22362265
});
22372266

22382267
@Component({
@@ -2619,3 +2648,21 @@ class AutocompleteWithNativeAutocompleteAttribute {
26192648
})
26202649
class InputWithoutAutocompleteAndDisabled {
26212650
}
2651+
2652+
@Component({
2653+
template: `
2654+
<input type="number" matInput [matAutocomplete]="autoOne">
2655+
<mat-autocomplete #autoOne>
2656+
<mat-option [value]="0">First</mat-option>
2657+
</mat-autocomplete>
2658+
2659+
<mat-autocomplete #autoTow>
2660+
<mat-option [value]="1">Second</mat-option>
2661+
</mat-autocomplete>
2662+
`,
2663+
})
2664+
class DynamicallyChangingAutocomplete {
2665+
@ViewChild('autoOne') autoOne: MatAutocomplete;
2666+
@ViewChild('autoTow') autoTow: MatAutocomplete;
2667+
@ViewChild(MatAutocompleteTrigger) trigger: MatAutocompleteTrigger;
2668+
}

‎src/lib/autocomplete/autocomplete.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import {
2424
TemplateRef,
2525
ViewChild,
2626
ViewEncapsulation,
27+
AfterViewInit,
28+
ViewContainerRef,
2729
} from '@angular/core';
2830
import {
2931
CanDisableRipple,
@@ -33,6 +35,7 @@ import {
3335
MatOption,
3436
mixinDisableRipple,
3537
} from '@angular/material/core';
38+
import {TemplatePortal} from '@angular/cdk/portal';
3639

3740

3841
/**
@@ -92,14 +95,17 @@ export function MAT_AUTOCOMPLETE_DEFAULT_OPTIONS_FACTORY(): MatAutocompleteDefau
9295
]
9396
})
9497
export class MatAutocomplete extends _MatAutocompleteMixinBase implements AfterContentInit,
95-
CanDisableRipple {
98+
AfterViewInit, CanDisableRipple {
9699

97100
/** Manages active item in option list based on key events. */
98101
_keyManager: ActiveDescendantKeyManager<MatOption>;
99102

100103
/** Whether the autocomplete panel should be visible, depending on option length. */
101104
showPanel: boolean = false;
102105

106+
/** @docs-private */
107+
_portal: TemplatePortal;
108+
103109
/** Whether the autocomplete panel is open. */
104110
get isOpen(): boolean { return this._isOpen && this.showPanel; }
105111
_isOpen: boolean = false;
@@ -165,12 +171,17 @@ export class MatAutocomplete extends _MatAutocompleteMixinBase implements AfterC
165171
constructor(
166172
private _changeDetectorRef: ChangeDetectorRef,
167173
private _elementRef: ElementRef<HTMLElement>,
174+
private _viewContainerRef: ViewContainerRef,
168175
@Inject(MAT_AUTOCOMPLETE_DEFAULT_OPTIONS) defaults: MatAutocompleteDefaultOptions) {
169176
super();
170177

171178
this._autoActiveFirstOption = !!defaults.autoActiveFirstOption;
172179
}
173180

181+
ngAfterViewInit() {
182+
this._portal = new TemplatePortal(this.template, this._viewContainerRef);
183+
}
184+
174185
ngAfterContentInit() {
175186
this._keyManager = new ActiveDescendantKeyManager<MatOption>(this.options).withWrap();
176187
// Set the initial visibility state.

0 commit comments

Comments
 (0)
Please sign in to comment.