【问题标题】:Correct way to implement wrapper components in Angular 4?在 Angular 4 中实现包装器组件的正确方法?
【发布时间】:2017-09-08 13:22:05
【问题描述】:

更新:

正确的问题可能是:“如何实现我自己的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>

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

<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 hack。

【问题讨论】:

  • 看起来你想使用 ng-content 使用“嵌入”:scotch.io/tutorials/angular-2-transclusion-using-ng-content 官方角度文档似乎缺少这个:github.com/angular/angular.io/issues/3099
  • 我已经大量使用 ng-content 来处理投影内容,但这并不能解决组件本身创建的包装器/容器标签的问题。我们可以使用带有指令而不是组件的 ng-container 来获得我们想要的东西,但这看起来很混乱。我们也许可以使用带有元素选择器的指令而不是默认的属性选择器,然后(也许?)使用 Renderer2/code 来渲染内容。我真正想要的是简单地说“在没有包装器/容器/宿主元素的情况下渲染此组件的内容。遗憾的是,我认为这是不可能的。

标签: angular wrapper


【解决方案1】:

我有一个可行的解决方案,虽然不像我希望的那样优雅。我更喜欢“摆脱宿主元素”的选项,因为这样会更整洁。

我通过简单地检查他们如何在 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 添加选项卡(和所有其他类型的控件),而不必担心是否为我们的项目选择了最佳组件 - 如果需要,我们可以在以后轻松切换.

(希望这不会带来性能问题!)

【讨论】:

    猜你喜欢
    • 2018-01-15
    • 1970-01-01
    • 1970-01-01
    • 2021-11-14
    • 2020-05-02
    • 2012-11-07
    • 1970-01-01
    • 1970-01-01
    • 2017-10-26
    相关资源
    最近更新 更多