【问题标题】:Angular CDK : attach overlay to a clicked elementAngular CDK:将覆盖附加到单击的元素
【发布时间】:2020-01-08 15:24:09
【问题描述】:

我正在尝试为表格单元格制作自定义弹出框,以便在单击单元格时显示单元格详细信息,其方式类似于mdBoostrap popovers

目前,我有以下应用:https://stackblitz.com/edit/angular-m5rx6j

Popup 组件显示在主组件下方,但我想将其显示在表格上方,就在我单击的元素下方。

我想我需要做以下事情:

  • 获取我单击的“td”的 ElementRef -> 我不知道如何
  • 将叠加层附加到此元素 -> 已经这样做了,但使用根元素

【问题讨论】:

  • 你最好从一些现成的解决方案开始,例如角度引导弹出窗口。
  • 我这样做是为了学习 Angular,但没有找到任何适用于 Angular 8.2 的示例(有一些适用于 Angular 5,但仅此而已)。
  • 我试图用点击的 td 替换 ElementRef。没有效果......另外,如果我将 positionStrategy 更改为其他内容,它也不起作用。问题可能来自其他地方请参阅:stackblitz.com/edit/angular-q1bza9
  • 另外,这个 stackblitz 可能很有用,因为它包含了一些非常接近你想要的东西stackblitz.com/edit/custom-overlay-step-4
  • 确实,我想我已经在另一个问题上看到了这个 Stackblitz。在展示如何制作全局叠加层的同时,我更想做类似mdbootstrap.com/docs/angular/advanced/popovers 的事情。

标签: angular overlay angular-cdk


【解决方案1】:

Netanet Basal 的博客中有两篇关于使用 CDK 的 OverLay 的精彩文章

  1. Creating Powerful Components with Angular CDK
  2. Context Menus Made Easy with Angular CDK

我尝试简单化this stackblitz

基本上你有一个注入 Overlay 的服务

constructor(private overlay: Overlay) { }

要打开一个模板,你需要传递源(我称他为“origin”)、模板(我称之为菜单)和组件的 viewContainerRef

    this.overlayRef = this.overlay.create(
        this.getOverlayConfig({ origin: origin})
    );
    //I can pass "data" as implicit and "close" to close the menu
    this.overlayRef.attach(new TemplatePortal(menu, viewContainerRef, {
        $implicit: data, close:this.close
    }));

getOverLayConfig 返回一个类似的配置

private getOverlayConfig({ origin}): OverlayConfig {
    return new OverlayConfig({
        hasBackdrop: false,
        backdropClass: "popover-backdrop",
        positionStrategy: this.getOverlayPosition(origin),
        scrollStrategy: this.overlay.scrollStrategies.close()
    });
}

职位策略是您要附加模板的位置 - 一个包含您首选职位的数组,例如

      [
        {
            originX: "center",
            originY: "bottom",
            overlayX: "center",
            overlayY: "top"
        },
      ]

好吧,代码的另一部分是关于关闭模板元素的。我选择在服务中创建一个打开的函数

1.-附加元素

2.-创建订阅

this.sub = fromEvent<MouseEvent>(document, "click")

3.-返回一个返回 null 的 observable 或你在函数中传递的参数 "close"(*)

注意:不要忘记包含在您的 css 中 ~@angular/cdk/overlay-prebuilt.css

(*) 这允许我像模板一样

<ng-template #tpl let-close="close" let-data>
  <div class="popover" >
    <h5>{{name}} {{data.data}}</h5> //<--name is a variable of component
                                    //data.data a variable you can pass
  And here's some amazing content. It's very engaging. Right?
  <div>
   <a (click)="close('uno')">Close</a> //<--this close and return 'uno'
  </div>
  </div>
</ng-template>

更新如果我们想先附加一个组件,我们需要记住它必须在模块的 entryComponents 中

@NgModule({
  imports:      [ BrowserModule, FormsModule,OverlayModule ],
  declarations: [ AppComponent,HelloComponent], //<--HERE
  bootstrap:    [ AppComponent ],
  entryComponents:[HelloComponent]  //<--and HERE

})

嗯,附加组件很简单,更改附加并使用 ComponentPortal,例如

const ref=this.overlayRef.attach(new ComponentPortal(HelloComponent,viewContainerRef))

那么,如果我们的组件有一些输入,例如

  @Input() name="Angular";
  @Input() obj={count:0};

我们可以使用 ref.instance 来访问组件,例如

  ref.instance.name="New Name"

但是由于我们要维护服务最一般的用途,我想使用参数“data”给变量赋值,所以我们的函数“open”变成了

open(origin: any, component: any, viewContainerRef: ViewContainerRef, data: any) {
        this.close(null);
        this.overlayRef = this.overlay.create(
            this.getOverlayConfig({ origin: origin})
        );
        const ref=this.overlayRef.attach(new ComponentPortal(component, viewContainerRef));
    for (let key in data) //here pass all the data to our component
    {
       ref.instance[key]=data[key]
    } 
    ...rest of code...
}

和往常一样,如果我们传递一个对象,组件中的所有更改都会改变对象的属性,因此在我们的主组件中可以做出一些喜欢

obj={count:2}

open(origin:any,menu:any,index:number)
  {
    this.popupService.open(origin,HelloComponent,this.viewContainerRef,
        {name:'new Name'+index,obj:this.obj})
    .subscribe(res=>{
      console.log(res)
    })
  }

看到,当我将对象作为 obj 传递时,组件中的任何更改都会更改对象的属性,在我的情况下,组件非常简单

@Component({
  selector: 'component',
  template:`Hello {{name}}
    <button (click)="obj.count=obj.count+1">click</button>
  `
})
export class HelloComponent  {
  @Input() name="Angular";
  @Input() obj={count:0};
}

你可以在new stackblitz看到

Update2 要从 HelloComponent 关闭面板,我们需要将服务作为公共注入并使用 close。或多或少,一个按钮

<button (click)="popupService.close(4)">close</button>

注入服务的位置

constructor(public popupService: MenuContextualService){}

【讨论】:

  • 感谢您的回答,它成功了。是否可以添加一个带有输入的 ComponentPortal 示例?
【解决方案2】:

要获取元素的引用,可以使用模板标识符:#this_element

你可以直接使用组件模板里面的值,或者从@ViewChild/@ViewChildren获取一个Typescript变量。

例如在您的代码中:

<td #this_element>
  <div (click)="open(this_element)">Overlay Host1</div>
</td>

您的open 函数现在将读取open(element: any)element,其类型取决于您放置#this_element 的位置。

你也可以通过

在你的组件代码中获取这个元素
@ViewChild('this_element', { static: true }) element;

如果你需要访问nativeElementAngular docs 中有一些警告,所以如果你在 web worker 中运行,请小心。在这种情况下,您可能想改用 Renderer2

【讨论】:

  • 它是否与 tbody 由“ngFor”填充的表兼容?
  • 是的,在这种情况下,您需要使用@ViewChildren,它会动态地为您提供与您提供的模板名称相对应的元素列表。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-26
  • 2018-07-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多