大家好。不久之前,下一个冲刺结束了,我花了一些时间让我的用户不是最需要的,但同时又有一些有趣的功能-用于处理我们的应用程序的交互式指南。互联网上有很多现成的解决方案-当然,所有解决方案都可以满足此任务,但是我们将亲自了解如何实现。建筑
该组件的体系结构非常简单。我们在DOM树中有一个重要的元素,我们想告诉用户一些东西,例如一个按钮。我们需要在该元素周围绘制一个变暗的层,从而将注意力转移到该元素上。您需要在此元素旁边画一张带有重要提示的卡片。
要解决此问题,@ angular / cdk将为我们提供帮助。在上一篇文章中,我已经赞扬@ angular / material,它取决于CDK,对我而言,它仍然是使用框架的所有功能创建的高质量组件的一个示例。@角度/材质库中的组件,例如菜单,对话框,工具栏使用CDK中的组件制作-叠加。该组件的简单界面允许您快速在我们的应用程序之上创建一个图层,该图层将独立调整以适应屏幕尺寸和滚动方式的变化。正如您已经了解的那样,使用该组件来解决我们的问题变得非常简单。首先,安装库npm i @angular/cdk @angular/material -S
安装后,别忘了向style.css添加样式@import '~@angular/cdk/overlay-prebuilt.css';
@import '~@angular/material/prebuilt-themes/deeppurple-amber.css';
现在在我们的项目中创建一个新模块:ng generate library intro-lib
并立即为指令生成模板:ng generate directive intro-trigger
该指令将触发我们将来的指南,听取元素上的点击并将其突出显示在页面上。@Directive({
selector: '[libIntroTrigger]'
})
export class IntroTriggerDirective {
@Input() libIntroTrigger: string;
constructor(private introLibService: IntroLibService, private elementRef: ElementRef) {}
@HostListener('click') showGuideMessage(): void {
this.introLibService.show$.emit([this.libIntroTrigger, this.elementRef]);
}
}
在这里,我们转到在创建库时也生成的服务,所有主要工作将由它完成。首先,在服务中声明一个新属性show$ = new EventEmitter<[string, ElementRef]>();
如我们所见,这些指令将向我们发送指令,在数组中,第一个元素是描述,第二个元素是我们描述的DOM元素(为简单起见,我选择此解决方案)。@Injectable({
providedIn: 'root'
})
export class IntroLibService {
private overlayRef: OverlayRef;
show$ = new EventEmitter<[string, ElementRef]>();
constructor(private readonly overlay: Overlay, private readonly ngZone: NgZone, private readonly injector: Injector) {
this.show$.subscribe(([description, elementRef]: [string, ElementRef]) => {
this.attach(elementRef, description);
});
}
}
通过添加对EventEmitter的更新的订阅来更新服务设计器,attach函数将接收更新并创建层。要创建图层,我们需要Overlay,Injector和NgZone。以下操作可以分为几个阶段:- 关闭当前叠加层(如果有)
- 建立位置策略
- 创建OverlayRef
- 创建PortalInjector
- 将组件附加到图层
从第一点很明显,为此,我们已经在服务中声明了属性。PositionStrategy-负责我们的图层如何在DOM树中定位。有几种现成的策略:- 灵活的连接位置策略
- 全球定位策略
简而言之,然后使用FlexibleConnectedPositionStrategy-将跟随特定的元素,并且根据配置,当您更改浏览器的大小或滚动时,它将坚持使用该元素,这是使用下拉列表,菜单的清晰示例。GlobalPositionStrategy-顾名思义,它是全局创建的,不需要任何元素即可工作,模态窗口就是一个明显的用法示例。添加一种方法来围绕要调查的元素创建浮动窗口策略。{
...
private getPositionStrategy(elementRef: ElementRef): PositionStrategy {
return this.overlay
.position()
.flexibleConnectedTo(elementRef)
.withViewportMargin(8)
.withGrowAfterOpen(true)
.withPositions([
{
originX: 'start',
originY: 'bottom',
overlayX: 'start',
overlayY: 'top'
},
{
originX: 'start',
originY: 'top',
overlayX: 'start',
overlayY: 'bottom'
},
{
originX: 'end',
originY: 'bottom',
overlayX: 'end',
overlayY: 'top'
},
{
originX: 'end',
originY: 'top',
overlayX: 'end',
overlayY: 'bottom'
}
]);
}
...
}
添加方法创建OverlayRefcreateOverlay(elementRef: ElementRef): OverlayRef {
const config = new OverlayConfig({
positionStrategy: this.getPositionStrategy(elementRef),
scrollStrategy: this.overlay.scrollStrategies.reposition()
});
return this.overlay.create(config);
}
并添加一种方法将我们的组件绑定到该层: attach(elementRef: ElementRef, description: string): void {
if (this.overlayRef && this.overlayRef.hasAttached()) {
this.overlayRef.dispose();
}
this.overlayRef = this.createOverlay(elementRef);
const dataRef = this.ngZone.run(
() => new DataRef(this.overlay, this.injector, this.overlayRef, elementRef, description)
);
const injector = new PortalInjector(this.injector, new WeakMap([[DATA_TOKEN, dataRef]]));
dataRef.overlayRef.attach(new ComponentPortal(IntroLibComponent, null, injector));
}
这就是显示消息的组件的样子@Component({
selector: 'lib-intro-lib',
template: `
<mat-card>
<mat-card-content> {{ data.description }}</mat-card-content>
</mat-card>
`,
styles: ['mat-card {width: 300px; margin: 32px;}']
})
export class IntroLibComponent {
constructor(@Inject(DATA_TOKEN) public data: DataRef) {}
}
关于已经列出的清单中给出的所有内容,除了DataRef。DataRef是一个简单的类,实际上我们将其添加到组件的注入器中,以传输数据进行渲染-例如描述。此外,我决定在其中绘制另一层以使元素变暗并突出显示。在这种情况下,我们将已经使用全局层创建策略。export class DataRef {
shadowOverlayRef: OverlayRef;
constructor(
private overlay: Overlay,
private injector: Injector,
public overlayRef: OverlayRef,
public elementRef: ElementRef,
public description: string
) {
const config = new OverlayConfig({
positionStrategy: this.overlay.position().global(),
scrollStrategy: this.overlay.scrollStrategies.block()
});
this.shadowOverlayRef = this.overlay.create(config);
this.shadowOverlayRef.attach(
new ComponentPortal(
ShadowOverlayComponent,
null,
new PortalInjector(this.injector, new WeakMap([[DATA_TOKEN, this.elementRef]]))
)
);
}
}
ShadowOverlayComponent-绘制一个组件,并且在注入器中仅使用需要强调的元素接收相同的标记。我是如何实现这一点的,您可以在github的源代码中看到,我不想单独关注这一点。我要说的是,我在全屏上绘制画布,在元素周围绘制形状,并使用fill('evenodd')方法填充上下文;总
最酷的是@ angular / cdk / overlay允许我们绘制任意多的图层。它们将具有适应性和灵活性。我们不必担心更改屏幕尺寸,否则元素会由于某些自然原因而发生变化,叠加层会为我们考虑一下。我们了解了如何使用图层,我们意识到创建逐步指南的任务并不那么困难。您可以通过添加以下功能来修改库:在元素之间切换,退出视图模式以及其他一些极端情况。谢谢您的关注。