@@ -25,8 +25,8 @@ import {
2525 OnDestroy ,
2626 OnInit ,
2727 Output ,
28- QueryList ,
2928 TemplateRef ,
29+ QueryList ,
3030 ViewChild ,
3131 ViewEncapsulation ,
3232} from '@angular/core' ;
@@ -36,7 +36,7 @@ import {matMenuAnimations} from './menu-animations';
3636import { MatMenuContent } from './menu-content' ;
3737import { throwMatMenuInvalidPositionX , throwMatMenuInvalidPositionY } from './menu-errors' ;
3838import { MatMenuItem } from './menu-item' ;
39- import { MatMenuPanel } from './menu-panel' ;
39+ import { MAT_MENU_PANEL , MatMenuPanel } from './menu-panel' ;
4040import { MenuPositionX , MenuPositionY } from './menu-positions' ;
4141
4242
@@ -84,18 +84,27 @@ const MAT_MENU_BASE_ELEVATION = 2;
8484 styleUrls : [ 'menu.css' ] ,
8585 changeDetection : ChangeDetectionStrategy . OnPush ,
8686 encapsulation : ViewEncapsulation . None ,
87+ exportAs : 'matMenu' ,
8788 animations : [
8889 matMenuAnimations . transformMenu ,
8990 matMenuAnimations . fadeInItems
9091 ] ,
91- exportAs : 'matMenu'
92+ providers : [
93+ { provide : MAT_MENU_PANEL , useExisting : MatMenu }
94+ ]
9295} )
93- export class MatMenu implements OnInit , AfterContentInit , MatMenuPanel , OnDestroy {
96+ export class MatMenu implements OnInit , AfterContentInit , MatMenuPanel < MatMenuItem > , OnDestroy {
9497 private _keyManager : FocusKeyManager < MatMenuItem > ;
9598 private _xPosition : MenuPositionX = this . _defaultOptions . xPosition ;
9699 private _yPosition : MenuPositionY = this . _defaultOptions . yPosition ;
97100 private _previousElevation : string ;
98101
102+ /** Menu items inside the current menu. */
103+ private _items : MatMenuItem [ ] = [ ] ;
104+
105+ /** Emits whenever the amount of menu items changes. */
106+ private _itemChanges = new Subject < MatMenuItem [ ] > ( ) ;
107+
99108 /** Subscription to tab events on the menu panel */
100109 private _tabSubscription = Subscription . EMPTY ;
101110
@@ -106,7 +115,10 @@ export class MatMenu implements OnInit, AfterContentInit, MatMenuPanel, OnDestro
106115 _panelAnimationState : 'void' | 'enter' = 'void' ;
107116
108117 /** Emits whenever an animation on the menu completes. */
109- _animationDone = new Subject < void > ( ) ;
118+ _animationDone = new Subject < AnimationEvent > ( ) ;
119+
120+ /** Whether the menu is animating. */
121+ _isAnimating : boolean ;
110122
111123 /** Parent menu of the current menu panel. */
112124 parentMenu : MatMenuPanel | undefined ;
@@ -142,7 +154,11 @@ export class MatMenu implements OnInit, AfterContentInit, MatMenuPanel, OnDestro
142154 /** @docs -private */
143155 @ViewChild ( TemplateRef ) templateRef : TemplateRef < any > ;
144156
145- /** List of the items inside of a menu. */
157+ /**
158+ * List of the items inside of a menu.
159+ * @deprecated
160+ * @deletion -target 7.0.0
161+ */
146162 @ContentChildren ( MatMenuItem ) items : QueryList < MatMenuItem > ;
147163
148164 /**
@@ -218,7 +234,7 @@ export class MatMenu implements OnInit, AfterContentInit, MatMenuPanel, OnDestro
218234 }
219235
220236 ngAfterContentInit ( ) {
221- this . _keyManager = new FocusKeyManager < MatMenuItem > ( this . items ) . withWrap ( ) . withTypeAhead ( ) ;
237+ this . _keyManager = new FocusKeyManager < MatMenuItem > ( this . _items ) . withWrap ( ) . withTypeAhead ( ) ;
222238 this . _tabSubscription = this . _keyManager . tabOut . subscribe ( ( ) => this . close . emit ( 'tab' ) ) ;
223239 }
224240
@@ -229,16 +245,10 @@ export class MatMenu implements OnInit, AfterContentInit, MatMenuPanel, OnDestro
229245
230246 /** Stream that emits whenever the hovered menu item changes. */
231247 _hovered ( ) : Observable < MatMenuItem > {
232- if ( this . items ) {
233- return this . items . changes . pipe (
234- startWith ( this . items ) ,
235- switchMap ( items => merge ( ...items . map ( item => item . _hovered ) ) )
236- ) ;
237- }
238-
239- return this . _ngZone . onStable
240- . asObservable ( )
241- . pipe ( take ( 1 ) , switchMap ( ( ) => this . _hovered ( ) ) ) ;
248+ return this . _itemChanges . pipe (
249+ startWith ( this . _items ) ,
250+ switchMap ( items => merge ( ...items . map ( item => item . _hovered ) ) )
251+ ) ;
242252 }
243253
244254 /** Handle a keyboard event from the menu, delegating to the appropriate action. */
@@ -322,6 +332,35 @@ export class MatMenu implements OnInit, AfterContentInit, MatMenuPanel, OnDestro
322332 }
323333 }
324334
335+ /**
336+ * Registers a menu item with the menu.
337+ * @docs -private
338+ */
339+ addItem ( item : MatMenuItem ) {
340+ // We register the items through this method, rather than picking them up through
341+ // `ContentChildren`, because we need the items to be picked up by their closest
342+ // `mat-menu` ancestor. If we used `@ContentChildren(MatMenuItem, {descendants: true})`,
343+ // all descendant items will bleed into the top-level menu in the case where the consumer
344+ // has `mat-menu` instances nested inside each other.
345+ if ( this . _items . indexOf ( item ) === - 1 ) {
346+ this . _items . push ( item ) ;
347+ this . _itemChanges . next ( this . _items ) ;
348+ }
349+ }
350+
351+ /**
352+ * Removes an item from the menu.
353+ * @docs -private
354+ */
355+ removeItem ( item : MatMenuItem ) {
356+ const index = this . _items . indexOf ( item ) ;
357+
358+ if ( this . _items . indexOf ( item ) > - 1 ) {
359+ this . _items . splice ( index , 1 ) ;
360+ this . _itemChanges . next ( this . _items ) ;
361+ }
362+ }
363+
325364 /** Starts the enter animation. */
326365 _startAnimation ( ) {
327366 // @deletion -target 7.0.0 Combine with _resetAnimation.
@@ -335,7 +374,8 @@ export class MatMenu implements OnInit, AfterContentInit, MatMenuPanel, OnDestro
335374 }
336375
337376 /** Callback that is invoked when the panel animation completes. */
338- _onAnimationDone ( ) {
339- this . _animationDone . next ( ) ;
377+ _onAnimationDone ( event : AnimationEvent ) {
378+ this . _animationDone . next ( event ) ;
379+ this . _isAnimating = false ;
340380 }
341381}
0 commit comments