在Angular 4中实现包装器组件的正确方法?

时间:2017-09-08 13:22:06

标签: angular wrapper

更新:

正确的问题可能是:&#34;如何实现我自己的ng-container版本,以便我可以使用&#34; <my-component ...>...</my-component>&#34;而不是&#34; <ng-container my-directive ...>...</ng-container>&#34;。

我正在Angular 4中开发一个新的应用程序。我想用包装器组件构建我的整个应用程序,这样我就可以根据需要更换实际的组件。这对于简单的控件很容易,但对于我们想要分解成单独组件的复杂控件则不行。最好的例子是使用标签,因为要求相当稳定:带有我们显示/隐藏的标签和内容面板的标签列表。

ng-bootstrap可能会使用以下内容:

<ngb-tabset>
  <ngb-tab title="Tab 1">
    <ng-template ngbTabContent>...</ng-template>
  </ngb-tab>
  ...
 <ngb-tabset>

我们可能还有其他一些组件:

<div class="my-tab-group">
  <ul>
    <li>Tab 1</li>
    ...
  </ul>
  <div class="my-tab" title="Tab 1">...</div>
  ...
</div>

此时我没有将我的应用程序绑定到特定的实现,而是想要定义自己的选项卡包装器并在任何地方使用它:

<my-tabs-control>
  <my-tab-control title="Tab 1">...</my-tab-control>
  ...
<my-tabs-control>

然后,如果我需要更改标签的工作方式,它会在一个地方发生。但是,如果我们使用包装器组件,那么HTML会受到与所需标签交错的主机元素标签的污染,这显然会使事情变得混乱,例如。

<my-tabs-control>
  <div class="my-tab-group">
    <ul>
      <li>Tab 1</li>
      ...
    </ul>
    <my-tab-control title="Tab 1">
      <div class="my-tab" title="Tab 1">...</div>
    </my-tab-control>        
    ...
  </div>
<my-tabs-control>

我猜这就是为什么很多人都会问如何在Angular组件中打开/删除主机元素的原因 - 这会让这很简单。

通常的答案是使用属性选择器,例如:

Remove the angular2 component's selector tag

How to remove/replace the angular2 component's selector tag from HTML

但是,这意味着我们需要知道使用网站上的标签结构,这显然会破坏使用精美命名的包装标签的全部意义。

因此,最后,我该怎么做呢?由于性能原因,它可能不可能吗?我已经开始关注Renderer2但是没有找到一种明显的方法来做我想做的事情,我想避免非角度的DOM黑客攻击。

1 个答案:

答案 0 :(得分:4)

我有一个有效的解决方案,虽然不像我想的那样优雅。我更倾向于选择“摆脱宿主元素”,因为这样会更加整洁。

我通过简单地检查他们如何在ng-bootstrap项目中实现标签来推导出这个解决方案:https://github.com/ng-bootstrap/ng-bootstrap

解决方案是使用@Component作为主容器,然后对所有内部子组件使用@Directives。通常我们使用指令作为其他元素的附加组件 - 但在这里我们使用指令作为实际元素。这似乎就像一个组件,它不会呈现它自己的内容,也不会创建自己的主机容器。相反,它允许主机组件决定如何处理指令的内容。额外的条件是我们必须使用ng-templates - 我不知道如何在不使用ng-templates的情况下直接投影指令内容。

以下是使用包装器组件标记选项卡容器的方法:

<my-tab-container>
  <my-tab title="Tab 1">
    <ng-template myTabContent>
      This is panel 1!
    </ng-template>
  </my-tab>
  <my-tab title="Tab 2">
    <ng-template myTabContent>
      This is panel 2!
    </ng-template>
  </my-tab>
  <my-tab title="Tab 3">
    <ng-template myTabContent>
      This is panel 3!
    </ng-template>
  </my-tab>
</my-tab-container>

这是我的选项卡包装器组件的最小实现,其中包括单个选项卡和选项卡内容的两个指令。

import { Component, OnInit, Directive, ContentChildren, QueryList, Input, TemplateRef, ContentChild } from '@angular/core';

@Directive({ selector: 'ng-template[myTabContent]' })
export class TabContentDirective {
  constructor(public templateRef: TemplateRef<any>) { }
}

@Directive({ selector: 'my-tab' })
export class TabDirective {
  @Input()
  title: string;
  @ContentChild(TabContentDirective)
  content: TabContentDirective;
  public getTemplateRef() {
    return this.content.templateRef;
  }
}

@Component({
  selector: 'my-tab-container',
  templateUrl: './tab-container.component.html',
  styleUrls: ['./tab-container.component.scss']
})
export class TabContainerComponent implements OnInit {
  @ContentChildren(TabDirective)
  tabs: QueryList<TabDirective>;
  constructor() { }
  ngOnInit() {
  }
}

现在,我可以通过为我的选项卡容器组件切换HTML模板来呈现不同类型的选项卡。

e.g。对于ng-bootstrap:

<ngb-tabset>
  <ngb-tab *ngFor="let tab of tabs">
    <ng-template ngbTabTitle>{{tab.title}}</ng-template>
    <ng-template ngbTabContent>
      <ng-template [ngTemplateOutlet]="tab.content.templateRef"></ng-template>
    </ng-template>
  </ngb-tab>
</ngb-tabset>

e.g。对于Angular Material2:

<md-tab-group>
  <md-tab *ngFor="let tab of tabs" label="{{tab.title}}">
    <ng-template [ngTemplateOutlet]="tab.content.templateRef"></ng-template> 
  </md-tab>
</md-tab-group>

或其他一些自定义的东西(jQueryUI样式):

<ul>
  <li *ngFor="let tab of tabs">{{tab.title}}</li>
</ul>
<div *ngFor="let tab of tabs">
  <ng-template [ngTemplateOutlet]="tab.content.templateRef"></ng-template>
</div>

这意味着我们现在可以在整个地方继续向用户界面添加标签(以及所有其他类型的控件),而无需担心我们是否为项目选择了最佳组件 - 如果需要,我们可以在以后轻松切换

(希望这不会引入性能问题!)