@@ -15,18 +15,18 @@ import {
1515 HorizontalConnectionPos ,
1616 OriginConnectionPosition ,
1717 Overlay ,
18- OverlayConfig ,
18+ ScrollDispatcher ,
1919 OverlayConnectionPosition ,
2020 OverlayRef ,
2121 RepositionScrollStrategy ,
2222 ScrollStrategy ,
2323 VerticalConnectionPos ,
24+ ConnectedPositionStrategy ,
2425} from '@angular/cdk/overlay' ;
2526import { Platform } from '@angular/cdk/platform' ;
2627import { ComponentPortal } from '@angular/cdk/portal' ;
2728import { take } from 'rxjs/operators/take' ;
28- import { merge } from 'rxjs/observable/merge' ;
29- import { ScrollDispatcher } from '@angular/cdk/scrolling' ;
29+ import { filter } from 'rxjs/operators/filter' ;
3030import {
3131 ChangeDetectionStrategy ,
3232 ChangeDetectorRef ,
@@ -108,6 +108,7 @@ export class MatTooltip implements OnDestroy {
108108 _overlayRef : OverlayRef | null ;
109109 _tooltipInstance : TooltipComponent | null ;
110110
111+ private _portal : ComponentPortal < TooltipComponent > ;
111112 private _position : TooltipPosition = 'below' ;
112113 private _disabled : boolean = false ;
113114 private _tooltipClass : string | string [ ] | Set < string > | { [ key : string ] : any } ;
@@ -119,10 +120,11 @@ export class MatTooltip implements OnDestroy {
119120 if ( value !== this . _position ) {
120121 this . _position = value ;
121122
122- // TODO(andrewjs): When the overlay's position can be dynamically changed, do not destroy
123- // the tooltip.
124- if ( this . _tooltipInstance ) {
125- this . _disposeTooltip ( ) ;
123+ if ( this . _overlayRef ) {
124+ // TODO(andrewjs): When the overlay's position can be
125+ // dynamically changed, do not destroy the tooltip.
126+ this . _detach ( ) ;
127+ this . _updatePosition ( ) ;
126128 }
127129 }
128130 }
@@ -236,15 +238,15 @@ export class MatTooltip implements OnDestroy {
236238 * Dispose the tooltip when destroyed.
237239 */
238240 ngOnDestroy ( ) {
239- if ( this . _tooltipInstance ) {
240- this . _disposeTooltip ( ) ;
241+ if ( this . _overlayRef ) {
242+ this . _overlayRef . dispose ( ) ;
243+ this . _tooltipInstance = null ;
241244 }
242245
243246 // Clean up the event listeners set in the constructor
244247 if ( ! this . _platform . IOS ) {
245- this . _manualListeners . forEach ( ( listener , event ) => {
246- this . _elementRef . nativeElement . removeEventListener ( event , listener ) ;
247- } ) ;
248+ this . _manualListeners . forEach ( ( listener , event ) =>
249+ this . _elementRef . nativeElement . removeEventListener ( event , listener ) ) ;
248250
249251 this . _manualListeners . clear ( ) ;
250252 }
@@ -257,10 +259,12 @@ export class MatTooltip implements OnDestroy {
257259 show ( delay : number = this . showDelay ) : void {
258260 if ( this . disabled || ! this . message ) { return ; }
259261
260- if ( ! this . _tooltipInstance ) {
261- this . _createTooltip ( ) ;
262- }
262+ const overlayRef = this . _createOverlay ( ) ;
263263
264+ this . _detach ( ) ;
265+ this . _portal = this . _portal || new ComponentPortal ( TooltipComponent , this . _viewContainerRef ) ;
266+ this . _tooltipInstance = overlayRef . attach ( this . _portal ) . instance ;
267+ this . _tooltipInstance . afterHidden ( ) . subscribe ( ( ) => this . _detach ( ) ) ;
264268 this . _setTooltipClass ( this . _tooltipClass ) ;
265269 this . _updateTooltipMessage ( ) ;
266270 this . _tooltipInstance ! . show ( this . _position , delay ) ;
@@ -296,73 +300,68 @@ export class MatTooltip implements OnDestroy {
296300 this . hide ( this . _defaultOptions ? this . _defaultOptions . touchendHideDelay : 1500 ) ;
297301 }
298302
299- /** Create the tooltip to display */
300- private _createTooltip ( ) : void {
301- const overlayRef = this . _createOverlay ( ) ;
302- const portal = new ComponentPortal ( TooltipComponent , this . _viewContainerRef ) ;
303-
304- this . _tooltipInstance = overlayRef . attach ( portal ) . instance ;
305-
306- // Dispose of the tooltip when the overlay is detached.
307- merge ( this . _tooltipInstance ! . afterHidden ( ) , overlayRef . detachments ( ) ) . subscribe ( ( ) => {
308- // Check first if the tooltip has already been removed through this components destroy.
309- if ( this . _tooltipInstance ) {
310- this . _disposeTooltip ( ) ;
311- }
312- } ) ;
313- }
314-
315303 /** Create the overlay config and position strategy */
316304 private _createOverlay ( ) : OverlayRef {
305+ if ( this . _overlayRef ) {
306+ return this . _overlayRef ;
307+ }
308+
317309 const origin = this . _getOrigin ( ) ;
318310 const overlay = this . _getOverlayPosition ( ) ;
319311
320312 // Create connected position strategy that listens for scroll events to reposition.
321313 const strategy = this . _overlay
322314 . position ( )
323315 . connectedTo ( this . _elementRef , origin . main , overlay . main )
324- . withFallbackPosition ( origin . fallback , overlay . fallback ) ;
325-
326- const scrollableAncestors = this . _scrollDispatcher
327- . getAncestorScrollContainers ( this . _elementRef ) ;
328-
329- strategy . withScrollableContainers ( scrollableAncestors ) ;
330-
331- strategy . onPositionChange . subscribe ( change => {
332- if ( this . _tooltipInstance ) {
333- if ( change . scrollableViewProperties . isOverlayClipped && this . _tooltipInstance . isVisible ( ) ) {
334- // After position changes occur and the overlay is clipped by
335- // a parent scrollable then close the tooltip.
336- this . _ngZone . run ( ( ) => this . hide ( 0 ) ) ;
337- } else {
338- // Otherwise recalculate the origin based on the new position.
339- this . _tooltipInstance . _setTransformOrigin ( change . connectionPair ) ;
340- }
316+ . withFallbackPosition ( origin . fallback , overlay . fallback )
317+ . withScrollableContainers (
318+ this . _scrollDispatcher . getAncestorScrollContainers ( this . _elementRef )
319+ ) ;
320+
321+ strategy . onPositionChange . pipe ( filter ( ( ) => ! ! this . _tooltipInstance ) ) . subscribe ( change => {
322+ if ( change . scrollableViewProperties . isOverlayClipped && this . _tooltipInstance ! . isVisible ( ) ) {
323+ // After position changes occur and the overlay is clipped by
324+ // a parent scrollable then close the tooltip.
325+ this . _ngZone . run ( ( ) => this . hide ( 0 ) ) ;
326+ } else {
327+ // Otherwise recalculate the origin based on the new position.
328+ this . _tooltipInstance ! . _setTransformOrigin ( change . connectionPair ) ;
341329 }
342330 } ) ;
343331
344- const config = new OverlayConfig ( {
332+ this . _overlayRef = this . _overlay . create ( {
345333 direction : this . _dir ? this . _dir . value : 'ltr' ,
346334 positionStrategy : strategy ,
347335 panelClass : TOOLTIP_PANEL_CLASS ,
348336 scrollStrategy : this . _scrollStrategy ( )
349337 } ) ;
350338
351- this . _overlayRef = this . _overlay . create ( config ) ;
339+ this . _overlayRef . detachments ( ) . subscribe ( ( ) => this . _detach ( ) ) ;
352340
353341 return this . _overlayRef ;
354342 }
355343
356- /** Disposes the current tooltip and the overlay it is attached to */
357- private _disposeTooltip ( ) : void {
358- if ( this . _overlayRef ) {
359- this . _overlayRef . dispose ( ) ;
360- this . _overlayRef = null ;
344+ /** Detaches the currently-attached tooltip. */
345+ private _detach ( ) {
346+ if ( this . _overlayRef && this . _overlayRef . hasAttached ( ) ) {
347+ this . _overlayRef . detach ( ) ;
361348 }
362349
363350 this . _tooltipInstance = null ;
364351 }
365352
353+ /** Updates the position of the current tooltip. */
354+ private _updatePosition ( ) {
355+ const position = this . _overlayRef ! . getConfig ( ) . positionStrategy as ConnectedPositionStrategy ;
356+ const origin = this . _getOrigin ( ) ;
357+ const overlay = this . _getOverlayPosition ( ) ;
358+
359+ position
360+ . withPositions ( [ ] )
361+ . withFallbackPosition ( origin . main , overlay . main )
362+ . withFallbackPosition ( origin . fallback , overlay . fallback ) ;
363+ }
364+
366365 /**
367366 * Returns the origin position and a fallback position based on the user's position preference.
368367 * The fallback position is the inverse of the origin (e.g. `'below' -> 'above'`).
0 commit comments