Skip to content

Commit 6286ecc

Browse files
committed
feat(autocomplete): add updatePosition() method to MatAutocompleteTrigger
Add an updatePosition() method to MatAutocompleteTrigger that re-evaluates the position of the autocomplete panel overlay and falls back to the above position if necessary. This is useful if enough options have been added to the panel while it is open to cause it to exceed the viewport. Fixes #11492
1 parent c276e26 commit 6286ecc

File tree

2 files changed

+55
-0
lines changed

2 files changed

+55
-0
lines changed

src/lib/autocomplete/autocomplete-trigger.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,16 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
224224
}
225225
}
226226

227+
/**
228+
* Updates the position of the autocomplete suggestion panel to ensure that it fits all options
229+
* within the viewport.
230+
*/
231+
updatePosition(): void {
232+
if (this._overlayAttached) {
233+
this._overlayRef!.updatePosition();
234+
}
235+
}
236+
227237
/**
228238
* A stream of actions that should close the autocomplete panel, including
229239
* when an option is selected, on blur, and when TAB is pressed.

src/lib/autocomplete/autocomplete.spec.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1478,6 +1478,50 @@ describe('MatAutocomplete', () => {
14781478
.toEqual(Math.floor(panelBottom), `Expected panel to stay aligned after filtering.`);
14791479
}));
14801480

1481+
it('should fall back to above position when requested if options are added while ' +
1482+
'the panel is open', fakeAsync(() => {
1483+
let fixture = createComponent(AutocompleteWithOnPushDelay);
1484+
fixture.detectChanges();
1485+
1486+
let inputEl = fixture.debugElement.query(By.css('input')).nativeElement;
1487+
let inputReference = fixture.debugElement.query(By.css('.mat-form-field-flex')).nativeElement;
1488+
1489+
// Push the element down so it has a little bit of space, but not enough to render.
1490+
inputReference.style.bottom = '10px';
1491+
inputReference.style.position = 'fixed';
1492+
1493+
// Focus the input to load the deferred options.
1494+
dispatchFakeEvent(inputEl, 'focusin');
1495+
tick(1000);
1496+
1497+
fixture.detectChanges();
1498+
tick();
1499+
1500+
const inputBottom = inputReference.getBoundingClientRect().bottom;
1501+
const panel = overlayContainerElement.querySelector('.mat-autocomplete-panel')!;
1502+
const panelTop = panel.getBoundingClientRect().top;
1503+
1504+
expect(Math.floor(inputBottom))
1505+
.toEqual(Math.floor(panelTop),
1506+
`Expected panel top to be below input before repositioning.`);
1507+
1508+
// Request a position update now that there are too many suggestions to fit in the viewport.
1509+
fixture.componentInstance.trigger.updatePosition();
1510+
1511+
const inputTop = inputReference.getBoundingClientRect().top;
1512+
const panelBottom = panel.getBoundingClientRect().bottom;
1513+
1514+
expect(Math.floor(inputTop))
1515+
.toEqual(Math.floor(panelBottom),
1516+
`Expected panel to fall back to above position after repositioning.`);
1517+
}));
1518+
1519+
it('should not throw if a panel reposition is requested while the panel is closed', () => {
1520+
let fixture = createComponent(SimpleAutocomplete);
1521+
fixture.detectChanges();
1522+
1523+
expect(() => fixture.componentInstance.trigger.updatePosition()).not.toThrow();
1524+
});
14811525
});
14821526

14831527
describe('Option selection', () => {
@@ -2198,6 +2242,7 @@ class AutocompleteWithNumbers {
21982242
`
21992243
})
22002244
class AutocompleteWithOnPushDelay implements OnInit {
2245+
@ViewChild(MatAutocompleteTrigger) trigger: MatAutocompleteTrigger;
22012246
options: string[];
22022247

22032248
ngOnInit() {

0 commit comments

Comments
 (0)