@@ -14,6 +14,7 @@ import {
14
14
OverlayRef ,
15
15
PositionStrategy ,
16
16
ScrollStrategy ,
17
+ ConnectedPosition ,
17
18
} from '@angular/cdk/overlay' ;
18
19
import { TemplatePortal } from '@angular/cdk/portal' ;
19
20
import { DOCUMENT } from '@angular/common' ;
@@ -31,6 +32,8 @@ import {
31
32
OnDestroy ,
32
33
Optional ,
33
34
ViewContainerRef ,
35
+ OnChanges ,
36
+ SimpleChanges ,
34
37
} from '@angular/core' ;
35
38
import { ViewportRuler } from '@angular/cdk/scrolling' ;
36
39
import { ControlValueAccessor , NG_VALUE_ACCESSOR } from '@angular/forms' ;
@@ -116,7 +119,7 @@ export function getMatAutocompleteMissingPanelError(): Error {
116
119
exportAs : 'matAutocompleteTrigger' ,
117
120
providers : [ MAT_AUTOCOMPLETE_VALUE_ACCESSOR ]
118
121
} )
119
- export class MatAutocompleteTrigger implements ControlValueAccessor , OnDestroy {
122
+ export class MatAutocompleteTrigger implements ControlValueAccessor , OnChanges , OnDestroy {
120
123
private _overlayRef : OverlayRef | null ;
121
124
private _portal : TemplatePortal ;
122
125
private _componentDestroyed = false ;
@@ -169,6 +172,15 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
169
172
/** The autocomplete panel to be attached to this trigger. */
170
173
@Input ( 'matAutocomplete' ) autocomplete : MatAutocomplete ;
171
174
175
+ /**
176
+ * Position of the autocomplete panel relative to the trigger element. A position of `auto`
177
+ * will render the panel underneath the trigger if there is enough space for it to fit in
178
+ * the viewport, otherwise the panel will be shown above it. If the position is set to
179
+ * `above` or `below`, the panel will always be shown above or below the trigger. no matter
180
+ * whether it fits completely in the viewport.
181
+ */
182
+ @Input ( 'matAutocompletePosition' ) position : 'auto' | 'above' | 'below' = 'auto' ;
183
+
172
184
/**
173
185
* Reference relative to which to position the autocomplete panel.
174
186
* Defaults to the autocomplete trigger element.
@@ -211,6 +223,16 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
211
223
this . _scrollStrategy = scrollStrategy ;
212
224
}
213
225
226
+ ngOnChanges ( changes : SimpleChanges ) {
227
+ if ( changes [ 'position' ] && this . _positionStrategy ) {
228
+ this . _setStrategyPositions ( this . _positionStrategy ) ;
229
+
230
+ if ( this . panelOpen ) {
231
+ this . _overlayRef ! . updatePosition ( ) ;
232
+ }
233
+ }
234
+ }
235
+
214
236
ngOnDestroy ( ) {
215
237
if ( typeof window !== 'undefined' ) {
216
238
window . removeEventListener ( 'blur' , this . _windowBlurHandler ) ;
@@ -596,10 +618,8 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
596
618
} ) ;
597
619
}
598
620
} else {
599
- const position = overlayRef . getConfig ( ) . positionStrategy as FlexibleConnectedPositionStrategy ;
600
-
601
621
// Update the trigger, panel width and direction, in case anything has changed.
602
- position . setOrigin ( this . _getConnectedElement ( ) ) ;
622
+ this . _positionStrategy . setOrigin ( this . _getConnectedElement ( ) ) ;
603
623
overlayRef . updateSize ( { width : this . _getPanelWidth ( ) } ) ;
604
624
}
605
625
@@ -630,31 +650,47 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
630
650
}
631
651
632
652
private _getOverlayPosition ( ) : PositionStrategy {
633
- this . _positionStrategy = this . _overlay . position ( )
653
+ const strategy = this . _overlay . position ( )
634
654
. flexibleConnectedTo ( this . _getConnectedElement ( ) )
635
655
. withFlexibleDimensions ( false )
636
- . withPush ( false )
637
- . withPositions ( [
638
- {
639
- originX : 'start' ,
640
- originY : 'bottom' ,
641
- overlayX : 'start' ,
642
- overlayY : 'top'
643
- } ,
644
- {
645
- originX : 'start' ,
646
- originY : 'top' ,
647
- overlayX : 'start' ,
648
- overlayY : 'bottom' ,
649
-
650
- // The overlay edge connected to the trigger should have squared corners, while
651
- // the opposite end has rounded corners. We apply a CSS class to swap the
652
- // border-radius based on the overlay position.
653
- panelClass : 'mat-autocomplete-panel-above'
654
- }
655
- ] ) ;
656
+ . withPush ( false ) ;
657
+
658
+ this . _setStrategyPositions ( strategy ) ;
659
+ this . _positionStrategy = strategy ;
660
+ return strategy ;
661
+ }
662
+
663
+ /** Sets the positions on a position strategy based on the directive's input state. */
664
+ private _setStrategyPositions ( positionStrategy : FlexibleConnectedPositionStrategy ) {
665
+ const belowPosition : ConnectedPosition = {
666
+ originX : 'start' ,
667
+ originY : 'bottom' ,
668
+ overlayX : 'start' ,
669
+ overlayY : 'top'
670
+ } ;
671
+ const abovePosition : ConnectedPosition = {
672
+ originX : 'start' ,
673
+ originY : 'top' ,
674
+ overlayX : 'start' ,
675
+ overlayY : 'bottom' ,
676
+
677
+ // The overlay edge connected to the trigger should have squared corners, while
678
+ // the opposite end has rounded corners. We apply a CSS class to swap the
679
+ // border-radius based on the overlay position.
680
+ panelClass : 'mat-autocomplete-panel-above'
681
+ } ;
682
+
683
+ let positions : ConnectedPosition [ ] ;
684
+
685
+ if ( this . position === 'above' ) {
686
+ positions = [ abovePosition ] ;
687
+ } else if ( this . position === 'below' ) {
688
+ positions = [ belowPosition ] ;
689
+ } else {
690
+ positions = [ belowPosition , abovePosition ] ;
691
+ }
656
692
657
- return this . _positionStrategy ;
693
+ positionStrategy . withPositions ( positions ) ;
658
694
}
659
695
660
696
private _getConnectedElement ( ) : ElementRef {
0 commit comments