如何制作应用程序分步指南(如果您的项目在Angular上)

大家好。

不久之前,下一个冲刺结束了,我花了一些时间让我的用户不是最需要的,但同时又有一些有趣的功能-用于处理我们的应用程序的交互式指南。

互联网上有很多现成的解决方案-当然,所有解决方案都可以满足此任务,但是我们将亲自了解如何实现。

建筑


该组件的体系结构非常简单。

我们在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树中定位。

有几种现成的策略:

  1. 灵活的连接位置策略
  2. 全球定位策略

简而言之,然后使用

FlexibleConnectedPositionStrategy-将跟随特定的元素,并且根据配置,当您更改浏览器的大小或滚动时,它将坚持使用该元素,这是使用下拉列表,菜单的清晰示例。

GlobalPositionStrategy-顾名思义,它是全局创建的,不需要任何元素即可工作,模态窗口就是一个明显的用法示例。

添加一种方法来围绕要调查的元素创建浮动窗口策略。

{
 ...
private getPositionStrategy(elementRef: ElementRef): PositionStrategy {
    return this.overlay
      .position()
      .flexibleConnectedTo(elementRef)
      .withViewportMargin(8) //     
      .withGrowAfterOpen(true) //           (, exspansion panel  )
      .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'
        }
      ]);
  }
...
}

添加方法创建OverlayRef

createOverlay(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)
    ); //   ,      ,    ,  CD    -  

    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允许我们绘制任意多的图层。它们将具有适应性和灵活性。我们不必担心更改屏幕尺寸,否则元素会由于某些自然原因而发生变化,叠加层会为我们考虑一下。

我们了解了如何使用图层,我们意识到创建逐步指南的任务并不那么困难。

您可以通过添加以下功能来修改库:在元素之间切换,退出视图模式以及其他一些极端情况。

谢谢您的关注。

Source: https://habr.com/ru/post/undefined/


All Articles