/
menu-content.ts
110 lines (95 loc) · 3.42 KB
/
menu-content.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {DomPortalOutlet, TemplatePortal} from '@angular/cdk/portal';
import {DOCUMENT} from '@angular/common';
import {
ApplicationRef,
ChangeDetectorRef,
ComponentFactoryResolver,
Directive,
Inject,
InjectionToken,
Injector,
OnDestroy,
TemplateRef,
ViewContainerRef,
} from '@angular/core';
import {Subject} from 'rxjs';
/**
* Injection token that can be used to reference instances of `MatMenuContent`. It serves
* as alternative token to the actual `MatMenuContent` class which could cause unnecessary
* retention of the class and its directive metadata.
*/
export const MAT_MENU_CONTENT = new InjectionToken<MatMenuContent>('MatMenuContent');
@Directive()
export abstract class _MatMenuContentBase implements OnDestroy {
private _portal: TemplatePortal<any>;
private _outlet: DomPortalOutlet;
/** Emits when the menu content has been attached. */
readonly _attached = new Subject<void>();
constructor(
private _template: TemplateRef<any>,
private _componentFactoryResolver: ComponentFactoryResolver,
private _appRef: ApplicationRef,
private _injector: Injector,
private _viewContainerRef: ViewContainerRef,
@Inject(DOCUMENT) private _document: any,
private _changeDetectorRef?: ChangeDetectorRef) {}
/**
* Attaches the content with a particular context.
* @docs-private
*/
attach(context: any = {}) {
if (!this._portal) {
this._portal = new TemplatePortal(this._template, this._viewContainerRef);
}
this.detach();
if (!this._outlet) {
this._outlet = new DomPortalOutlet(this._document.createElement('div'),
this._componentFactoryResolver, this._appRef, this._injector);
}
const element: HTMLElement = this._template.elementRef.nativeElement;
// Because we support opening the same menu from different triggers (which in turn have their
// own `OverlayRef` panel), we have to re-insert the host element every time, otherwise we
// risk it staying attached to a pane that's no longer in the DOM.
element.parentNode!.insertBefore(this._outlet.outletElement, element);
// When `MatMenuContent` is used in an `OnPush` component, the insertion of the menu
// content via `createEmbeddedView` does not cause the content to be seen as "dirty"
// by Angular. This causes the `@ContentChildren` for menu items within the menu to
// not be updated by Angular. By explicitly marking for check here, we tell Angular that
// it needs to check for new menu items and update the `@ContentChild` in `MatMenu`.
// @breaking-change 9.0.0 Make change detector ref required
if (this._changeDetectorRef) {
this._changeDetectorRef.markForCheck();
}
this._portal.attach(this._outlet, context);
this._attached.next();
}
/**
* Detaches the content.
* @docs-private
*/
detach() {
if (this._portal.isAttached) {
this._portal.detach();
}
}
ngOnDestroy() {
if (this._outlet) {
this._outlet.dispose();
}
}
}
/**
* Menu content that will be rendered lazily once the menu is opened.
*/
@Directive({
selector: 'ng-template[matMenuContent]',
providers: [{provide: MAT_MENU_CONTENT, useExisting: MatMenuContent}],
})
export class MatMenuContent extends _MatMenuContentBase {}