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