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

标签 angular wrapper

更新:

正确的问题可能是:“如何实现我自己的 ng-container 版本以便我可以使用” <my-component ...>...</my-component> “而不是”<ng-container my-directive ...>...</ng-container> ".

我正在使用 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>

此时我不想将我的应用程序绑定(bind)到特定的实现,而是想定义我自己的选项卡包装器并像这样在任何地方使用它:

<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,但还没有找到一种明显的方法来做我想做的事情,我想避免非 Angular DOM 黑客攻击。

最佳答案

我有一个可行的解决方案,尽管不如我希望的那么优雅。我更喜欢“摆脱主机元素”的选项,因为那样会更整洁。

我通过简单地检查他们如何在 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 模板来呈现不同类型的选项卡。

例如对于 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>

例如对于 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>

这意味着我们现在可以在所有地方向 UI 添加选项卡(和所有其他类型的控件),而不必担心我们是否为我们的项目选择了最好的组件 - 如果需要,我们可以稍后轻松切换.

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

关于angular - 在 Angular 4 中实现包装器组件的正确方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46117522/

相关文章:

ios - 在 Swift 属性包装器中支持可选参数值

.net - SharpGen 对于 SharpDX 以外的其他项目有用吗?还是我应该使用 SWIG?

.net - 从具有不同 GUID 的 .Net 引用 COM+

Angular CLI Assets 文件夹不会进入 dist 文件夹

javascript - angular 2 禁用 url 编码

angular - 将 Observable 数据从父组件传递到使用 Angular 2+ 中的 componentFactoryResolver 创建的子组件

Angular Chrome Api 数据绑定(bind)不起作用

angular - 从 Angular 4 中的 API 获取数据

c++ - C# 中的 inet_addr 函数等效于什么

python - 用于包装尝试的通用装饰器除了在 python 中?