@@ -104,11 +104,11 @@ export class MatMenu implements AfterContentInit, MatMenuPanel<MatMenuItem>, OnI
104
104
private _yPosition : MenuPositionY = this . _defaultOptions . yPosition ;
105
105
private _previousElevation : string ;
106
106
107
- /** Menu items inside the current menu. */
108
- private _items : MatMenuItem [ ] = [ ] ;
107
+ /** All items inside the menu. Includes items nested inside another menu. */
108
+ @ ContentChildren ( MatMenuItem , { descendants : true } ) _allItems : QueryList < MatMenuItem > ;
109
109
110
- /** Emits whenever the amount of menu items changes . */
111
- private _itemChanges = new Subject < MatMenuItem [ ] > ( ) ;
110
+ /** Only the direct descendant menu items. */
111
+ private _directDescendantItems = new QueryList < MatMenuItem > ( ) ;
112
112
113
113
/** Subscription to tab events on the menu panel */
114
114
private _tabSubscription = Subscription . EMPTY ;
@@ -248,23 +248,41 @@ export class MatMenu implements AfterContentInit, MatMenuPanel<MatMenuItem>, OnI
248
248
}
249
249
250
250
ngAfterContentInit ( ) {
251
- this . _keyManager = new FocusKeyManager < MatMenuItem > ( this . _items ) . withWrap ( ) . withTypeAhead ( ) ;
251
+ this . _updateDirectDescendants ( ) ;
252
+ this . _keyManager = new FocusKeyManager ( this . _directDescendantItems ) . withWrap ( ) . withTypeAhead ( ) ;
252
253
this . _tabSubscription = this . _keyManager . tabOut . subscribe ( ( ) => this . closed . emit ( 'tab' ) ) ;
253
254
}
254
255
255
256
ngOnDestroy ( ) {
257
+ this . _directDescendantItems . destroy ( ) ;
256
258
this . _tabSubscription . unsubscribe ( ) ;
257
259
this . closed . complete ( ) ;
258
260
}
259
261
260
262
/** Stream that emits whenever the hovered menu item changes. */
261
263
_hovered ( ) : Observable < MatMenuItem > {
262
- return this . _itemChanges . pipe (
263
- startWith ( this . _items ) ,
264
- switchMap ( items => merge ( ...items . map ( item => item . _hovered ) ) )
264
+ return this . _directDescendantItems . changes . pipe (
265
+ startWith ( this . _directDescendantItems ) ,
266
+ switchMap ( items => merge ( ...items . map ( ( item : MatMenuItem ) => item . _hovered ) ) )
265
267
) ;
266
268
}
267
269
270
+ /*
271
+ * Registers a menu item with the menu.
272
+ * @docs -private
273
+ * @deprecated No longer being used. To be removed.
274
+ * @breaking -change 9.0.0
275
+ */
276
+ addItem ( _item : MatMenuItem ) { }
277
+
278
+ /**
279
+ * Removes an item from the menu.
280
+ * @docs -private
281
+ * @deprecated No longer being used. To be removed.
282
+ * @breaking -change 9.0.0
283
+ */
284
+ removeItem ( _item : MatMenuItem ) { }
285
+
268
286
/** Handle a keyboard event from the menu, delegating to the appropriate action. */
269
287
_handleKeydown ( event : KeyboardEvent ) {
270
288
const keyCode = event . keyCode ;
@@ -334,35 +352,6 @@ export class MatMenu implements AfterContentInit, MatMenuPanel<MatMenuItem>, OnI
334
352
}
335
353
}
336
354
337
- /**
338
- * Registers a menu item with the menu.
339
- * @docs -private
340
- */
341
- addItem ( item : MatMenuItem ) {
342
- // We register the items through this method, rather than picking them up through
343
- // `ContentChildren`, because we need the items to be picked up by their closest
344
- // `mat-menu` ancestor. If we used `@ContentChildren(MatMenuItem, {descendants: true})`,
345
- // all descendant items will bleed into the top-level menu in the case where the consumer
346
- // has `mat-menu` instances nested inside each other.
347
- if ( this . _items . indexOf ( item ) === - 1 ) {
348
- this . _items . push ( item ) ;
349
- this . _itemChanges . next ( this . _items ) ;
350
- }
351
- }
352
-
353
- /**
354
- * Removes an item from the menu.
355
- * @docs -private
356
- */
357
- removeItem ( item : MatMenuItem ) {
358
- const index = this . _items . indexOf ( item ) ;
359
-
360
- if ( this . _items . indexOf ( item ) > - 1 ) {
361
- this . _items . splice ( index , 1 ) ;
362
- this . _itemChanges . next ( this . _items ) ;
363
- }
364
- }
365
-
366
355
/**
367
356
* Adds classes to the menu panel based on its position. Can be used by
368
357
* consumers to add specific styling based on the position.
@@ -409,4 +398,19 @@ export class MatMenu implements AfterContentInit, MatMenuPanel<MatMenuItem>, OnI
409
398
event . element . scrollTop = 0 ;
410
399
}
411
400
}
401
+
402
+ /**
403
+ * Sets up a stream that will keep track of any newly-added menu items and will update the list
404
+ * of direct descendants. We collect the descendants this way, because `_allItems` can include
405
+ * items that are part of child menus, and using a custom way of registering items is unreliable
406
+ * when it comes to maintaining the item order.
407
+ */
408
+ private _updateDirectDescendants ( ) {
409
+ this . _allItems . changes
410
+ . pipe ( startWith ( this . _allItems ) )
411
+ . subscribe ( ( items : QueryList < MatMenuItem > ) => {
412
+ this . _directDescendantItems . reset ( items . filter ( item => item . _parentMenu === this ) ) ;
413
+ this . _directDescendantItems . notifyOnChanges ( ) ;
414
+ } ) ;
415
+ }
412
416
}
0 commit comments