@@ -18,7 +18,9 @@ import {
1818 Optional ,
1919 Input ,
2020 HostListener ,
21+ NgZone ,
2122 AfterViewInit ,
23+ ViewChild ,
2224} from '@angular/core' ;
2325import {
2426 CanDisable ,
@@ -61,6 +63,9 @@ export class MatMenuItem extends _MatMenuItemBase
6163 /** ARIA role for the menu item. */
6264 @Input ( ) role : 'menuitem' | 'menuitemradio' | 'menuitemcheckbox' = 'menuitem' ;
6365
66+ /** Reference to the element wrapping the projected content. */
67+ @ViewChild ( 'content' ) _content : ElementRef < HTMLElement > | undefined ;
68+
6469 /** Stream that emits when the menu item is hovered. */
6570 readonly _hovered : Subject < MatMenuItem > = new Subject < MatMenuItem > ( ) ;
6671
@@ -81,9 +86,11 @@ export class MatMenuItem extends _MatMenuItemBase
8186 */
8287 @Inject ( DOCUMENT ) _document ?: any ,
8388 private _focusMonitor ?: FocusMonitor ,
84- @Inject ( MAT_MENU_PANEL ) @Optional ( ) public _parentMenu ?: MatMenuPanel < MatMenuItem > ) {
89+ @Inject ( MAT_MENU_PANEL ) @Optional ( ) public _parentMenu ?: MatMenuPanel < MatMenuItem > ,
90+ private _ngZone ?: NgZone ) {
8591
8692 // @breaking -change 8.0.0 make `_focusMonitor` and `document` required params.
93+ // @breaking -change 11.0.0 make `_ngZone` a required parameter.
8794 super ( ) ;
8895
8996 if ( _parentMenu && _parentMenu . addItem ) {
@@ -109,6 +116,13 @@ export class MatMenuItem extends _MatMenuItemBase
109116 // mouse or touch interaction.
110117 this . _focusMonitor . monitor ( this . _elementRef , false ) ;
111118 }
119+
120+ // @breaking -change 11.0.0 Remove null check for `_ngZone`.
121+ if ( this . _ngZone ) {
122+ this . _ngZone . runOutsideAngular ( ( ) => this . _bindDisabledClickEvents ( ) ) ;
123+ } else {
124+ this . _bindDisabledClickEvents ( ) ;
125+ }
112126 }
113127
114128 ngOnDestroy ( ) {
@@ -120,6 +134,11 @@ export class MatMenuItem extends _MatMenuItemBase
120134 this . _parentMenu . removeItem ( this ) ;
121135 }
122136
137+ this . _elementRef . nativeElement . removeEventListener ( 'click' , this . _preventDisabledClicks ) ;
138+ if ( this . _content ) {
139+ this . _content . nativeElement . removeEventListener ( 'click' , this . _preventDisabledClicks ) ;
140+ }
141+
123142 this . _hovered . complete ( ) ;
124143 this . _focused . complete ( ) ;
125144 }
@@ -134,20 +153,6 @@ export class MatMenuItem extends _MatMenuItemBase
134153 return this . _elementRef . nativeElement ;
135154 }
136155
137- /** Prevents the default element actions if it is disabled. */
138- // We have to use a `HostListener` here in order to support both Ivy and ViewEngine.
139- // In Ivy the `host` bindings will be merged when this class is extended, whereas in
140- // ViewEngine they're overwritten.
141- // TODO(crisbeto): we move this back into `host` once Ivy is turned on by default.
142- // tslint:disable-next-line:no-host-decorator-in-concrete
143- @HostListener ( 'click' , [ '$event' ] )
144- _checkDisabled ( event : Event ) : void {
145- if ( this . disabled ) {
146- event . preventDefault ( ) ;
147- event . stopPropagation ( ) ;
148- }
149- }
150-
151156 /** Emits to the hover stream. */
152157 // We have to use a `HostListener` here in order to support both Ivy and ViewEngine.
153158 // In Ivy the `host` bindings will be merged when this class is extended, whereas in
@@ -173,6 +178,26 @@ export class MatMenuItem extends _MatMenuItemBase
173178 return clone . textContent ?. trim ( ) || '' ;
174179 }
175180
181+ /** Binds the click events that prevent the default actions while disabled. */
182+ private _bindDisabledClickEvents ( ) {
183+ // We need to bind this event both on the root node and the content wrapper, because browsers
184+ // won't dispatch events on disabled `button` nodes, but they'll still be dispatched if the
185+ // user interacts with a non-disabled child of the button. This means that can get regions
186+ // inside a disabled menu item where clicks land and others where they don't.
187+ this . _elementRef . nativeElement . addEventListener ( 'click' , this . _preventDisabledClicks ) ;
188+ if ( this . _content ) {
189+ this . _content . nativeElement . addEventListener ( 'click' , this . _preventDisabledClicks ) ;
190+ }
191+ }
192+
193+ /** Prevents the default click action if the menu item is disabled. */
194+ private _preventDisabledClicks = ( event : Event ) => {
195+ if ( this . disabled ) {
196+ event . preventDefault ( ) ;
197+ event . stopPropagation ( ) ;
198+ }
199+ }
200+
176201 static ngAcceptInputType_disabled : BooleanInput ;
177202 static ngAcceptInputType_disableRipple : BooleanInput ;
178203}
0 commit comments