使用Angular Component Dev Kit创建简单的下拉菜单
首先
在Angular Material中,我发现没有像菜单和自动完成这样的可编辑内容的下拉菜单,因此我将使用Component Dev Kit (CDK)的Overlay模块创建一个可以编辑内容的下拉菜单模块。
目标
达成目标
实现目标
完成目标
追求目标

准备
安装Angular和CDK。关于NodeJS略过。
$ npm install -g @angular/cli
$ ng new cdk-overlay-trial
$ cd cdk-overlay-trial/
$ npm install @angular/cdk
$ ng version
...
Angular CLI: 8.1.0
Node: 11.15.0
OS: darwin x64
Angular: 8.1.0
...
导入所需的模块。
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { OverlayModule } from '@angular/cdk/overlay'; // 追加
import { PortalModule } from '@angular/cdk/portal'; // 追加
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
OverlayModule, // 追加
PortalModule // 追加
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
记得不要忘记CSS。
@import "~@angular/cdk/overlay-prebuilt.css";
实施 – 制作外观
我会先从外观开始写。将模板引用变量”dropdownContent”传递给”appDropdownToggle”属性。”appDropdownToggle”是一个属性指令。将下拉菜单的模板传递给这个指令,并在其中执行显示下拉菜单的操作。
“app-dropdown-content”被指定为一个模板引用变量,用于组件,为了显示Overlay,将其提前进行portal化。
<button [appDropdownToggle]="dropdownContent">show dropdown</button>
<app-dropdown-content #dropdownContent>
<div>some content</div>
</app-dropdown-content>
我们将创建前文中所述的组件和指令。
dropdown-content : Overlay表示となるドロップダウンの本体です。
dropdown-trigger : ドロップダウンの表示/非表示を制御するディレクティブです。
$ ng generate component dropdown-content
$ ng generate directive dropdown-content/dropdown-toggle
我们可以使用创建的DropdownToggleDirective来接收模板引用变量。
import { Directive, Input } from '@angular/core';
import { DropdownContentComponent } from './dropdown-content.component';
...
export class DropdownToggleDirective {
@Input('appDropdownToggle') dropdownContentRef: DropdownContentComponent;
...
}

<ng-template cdk-portal>
<div class="dropdown-wrapper">
<ng-content></ng-content>
</div>
</ng-template>
顺便设置样式,让下拉菜单看起来更合适。
如果你使用 Angular Material,我认为你可以使用 elevation helpers 来替代 box-shadow 部分。
.dropdown-wrapper {
background-color: #FFF;
border-radius: 4px;
box-shadow: 0 2px 4px -1px rgba(0,0,0,.2), 0 4px 5px 0 rgba(0,0,0,.14), 0 1px 10px 0 rgba(0,0,0,.12);
}
实施-显示下拉菜单
显示Overlay
我們將使用DropdownToggleDirective來觸發。
我們將對showDropdown()方法添加@Hostlistener裝飾器。當按下按鈕時,將調用此方法以顯示下拉菜單。
另外,我們也應該創建一個hideDropdown()方法,用於隱藏下拉菜單。
export class DropdownToggleDirective {
...
@HostListener('click') showDropdown() {
}
hideDropdown() {
}
}
为了将实现的作为DropdownComponent的子元素的元素显示在下拉菜单中,我将确保DropdownToggleDirective可以引用它。
由于已经持有DropdownComponent的引用,所以只需要在@ViewChild中将其放置即可。
import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
...
export class DropdownContentComponent implements OnInit {
@ViewChild(TemplateRef, {static: false}) templateRef: TemplateRef<any>;
...
}
我们将在DropdownToggleDirective中添加显示下拉菜单的处理。创建Overlay容器,创建模板的实例并将实例附加到容器中。(不能确定这种理解或表达是否正确)
import { Directive, Input, HostListener, ViewContainerRef } from '@angular/core';
import { DropdownContentComponent } from './dropdown-content.component';
import { Overlay, OverlayRef } from '@angular/cdk/overlay';
import { TemplatePortal } from '@angular/cdk/portal';
...
export class DropdownToggleDirective {
...
overlayRef: OverlayRef;
constructor(
private overlay: Overlay,
private viewContainerRef: ViewContainerRef
) { }
@HostListener('click') showDropdown() {
this.overlayRef = this.overlay.create();
const templatePortal = new TemplatePortal(this.dropdownContentRef.templateRef, this.viewContainerRef);
this.overlayRef.attach(templatePortal);
}
...
}
我想,大致上当你点击按钮时会出现一个下拉菜单(或类似的东西)。
调整Overlay的位置
我会添加用于调整位置的设置。随后,还会添加其他不涉及位置调整的设置。
我会创建一个OverlayConfig,并在调用this.overlay.create()时传递并应用它。
import { Directive, Input, HostListener, ViewContainerRef, ElementRef } from '@angular/core';
import { Overlay, OverlayRef, OverlayConfig } from '@angular/cdk/overlay';
...
constructor(
private overlay: Overlay,
private viewContainerRef: ViewContainerRef,
private viewElement: ElementRef
) { }
@HostListener('click') showDropdown() {
const config = this._generateOverlayConfig();
this.overlayRef = this.overlay.create(config);
...
}
private _generateOverlayConfig(): OverlayConfig {
const overlayPosition = this.overlay.position()
.flexibleConnectedTo(this.viewElement)
.withPositions([{
originX: 'start',
originY: 'bottom',
overlayX: 'start',
overlayY: 'top'
}]);
return {
positionStrategy: overlayPosition
};
}
...
让我们稍微详细地讨论一下位置调整。
使用`.flexibleConnectedTo(this.viewElement)`来设置Overlay位置的参考点。其中`this.viewElement`是指该指令所在的DOM的引用。也就是说,在这种情况下,将使用button元素作为Overlay位置的参考点。
除了`.flexibleConnectedTo()`之外,还有`.global()`。正如其名称所示,它将位置的参考点设置为全局,即以整个窗口作为参考点来确定位置。当创建覆盖整个屏幕的模态框时,可以使用此选项。
使用.withPositions()来设置具体位置。可以指定的值如下:
-
- originX, overlayX: ‘start’ | ‘end’ | ‘center’
- originY, overlayY: ‘top’ | ‘bottom’ | ‘center’
除了上述之外,还有offsetX和offsetY,并且似乎可以进行微调。对于所设置的值会有什么效果,真的不是很清楚…我稍后会进行验证。虽然文件上有说明,但我可能理解不够…
隐藏覆盖层
如果保持当前的状态,无论点击下拉菜单做什么都无法隐藏它,所以我们将使其在点击除下拉菜单之外的元素时隐藏。
将hasBackdrop设为true可以阻止点击背面元素的操作等。另外,通过单独设置backdropClass,可以避免默认CSS样式被应用到背景层。如果不进行此设置,背景层会显示为灰色。
private _generateOverlayConfig(): OverlayConfig {
...
return {
positionStrategy: overlayPosition,
hasBackdrop: true,
backdropClass: 'cdk-overlay-transparent-backdrop'
};
}
添加一个处理来隐藏Dropdown的Overlay。然后,在附加了portal时,设置点击背景时隐藏。
@HostListener('click') showDropdown() {
...
this.overlayRef.attach(templatePortal);
this.overlayRef.backdropClick().subscribe(() => this.hideDropdown());
}
...
hideDropdown() {
this.overlayRef.dispose();
}
}
只需要对下拉菜单中的元素进行随意的样式设置,它就会看起来不错。
<button [appDropdownToggle]="dropdownContent">show dropdown</button>
<app-dropdown-content #dropdownContent>
<div style="padding: 8px">some content</div>
</app-dropdown-content>
结束 (Jie shu)
通过琢磨,我终于做出了一个可以多功能复用的下拉菜单。
正如 @Quramy 所说,这个样本真的很少。我自己查看了 Angular Material 的测试代码来确认使用方法。
希望这些功能能够更广泛地普及,因为它们非常实用。(在使用AngularJS时,我经常为那些隐藏在背后的下拉菜单感到困扰…)
请参考
-
- Angular Component Dev Kit 入門 – https://qiita.com/Quramy/items/caf015e56d536411d4ba
API reference for Angular CDK overlay – https://material.angular.io/cdk/overlay/api
How to create a custom dropdown using Angular CDK – http://prideparrot.com/blog/archive/2019/3/how_to_create_custom_dropdown_cdk