【问题标题】:Create a "clone" of an Angular2 component with ng-content使用 ng-content 创建 Angular2 组件的“克隆”
【发布时间】:2017-04-11 13:20:16
【问题描述】:

我正在使用 Angular 2 创建一个 3D“卡片翻转”。父“卡片翻转”组件包含一个嵌套的“卡片翻转正面”和“卡片翻转背面”组件。

<card-flip card-flip-id="demo-1" class="grid_col-6">
  <card-flip-front class="card">
    <div class="card__inner">
      Card Front
    </div>
  </card-flip-front>
  <card-flip-back class="card">
    <div class="card__inner">
      Card Back
    </div>
  </card-flip-back>
</card-flip>

我想创建一个卡片翻转前端组件的“克隆”,其中包含内容投影和数据绑定。 “克隆”将用于制作动画,而“原始”将保留在其隐藏的原始位置。这样,当“克隆”返回到原始位置(即使用户滚动或调整窗口大小)时,我就有了“克隆”应该动画到哪里的参考。

我面临的主要挑战是我需要将ng-content 标签中的内容也投射到“克隆”中。问题是 Angular 将使用第一个 ng-content 标签进行内容投影,而其他未标记的 ng-content 标签将为空(我知道这是预期的行为)。

有人可能会问,“为什么不直接在 DOM 中创建元素的静态副本呢?”。我想避免这种情况,以便注入数据(从而修改元素的尺寸)的嵌套组件和数据绑定将继续工作。

到目前为止,这是我的工作,它通过 ComponentFactory 创建了 CardFlipFront 组件的实例以用作“克隆”,并简单地插入“原始”CardFlipFront 的 innerHTML。

import { 
  Component,
  ComponentFactory,
  ComponentFactoryResolver,
  ComponentRef,
  ContentChild,
  Inject,
  Input,
  OnInit,
  ViewChild,
  ViewContainerRef 
} from '@angular/core';
import { CardFlipFrontComponent } from './card-flip-front.component';
import { CardFlipBackComponent } from './card-flip-back.component';
import { CardFlipService } from './card-flip.service';

@Component({
  selector: 'card-flip',
  templateUrl: './card-flip.component.html',
  styleUrls: ['./card-flip.component.css'],
  entryComponents: [
    CardFlipFrontComponent
  ]
})
export class CardFlipComponent implements OnInit {
  @Input('card-flip-id') public id: string;
  @ContentChild(CardFlipFrontComponent) private front: CardFlipFrontComponent;
  @ContentChild(CardFlipBackComponent) private back: CardFlipBackComponent;
  @ViewChild('frontCloneContainer', { read: ViewContainerRef }) private frontCloneContainer: ViewContainerRef;
  private frontComponentRef: ComponentFactory<CardFlipFrontComponent>;
  private frontClone: ComponentRef<CardFlipFrontComponent>;

  constructor(
    @Inject(CardFlipService) private _cardFlipService: CardFlipService,
    private _componentFactoryResolver: ComponentFactoryResolver
  ) {
    this.frontComponentRef = this._componentFactoryResolver.resolveComponentFactory(CardFlipFrontComponent);
  }

  ngOnInit() {
    this._cardFlipService.register(this.id);
  }

  ngAfterViewInit() {
    // Create a card-flip-front component instance to serve as a "clone"
    this.frontClone = this.frontCloneContainer.createComponent(this.frontComponentRef);
    // Copy the innerHTML of the "original" into the "clone"
    this.frontClone.instance.el.nativeElement.innerHTML = this.front.el.nativeElement.innerHTML;
  }

  ngOnDestroy() {
    this.frontClone.destroy();
  }
}
<ng-content select="card-flip-front"></ng-content>
<ng-container #frontCloneContainer></ng-container>
<ng-content select="card-flip-back"></ng-content>

import {
  Component,
  ElementRef,
  HostBinding,
  Input,
  OnInit,
  Renderer
} from '@angular/core';

@Component({
  selector: 'card-flip-front',
  templateUrl: './card-flip-front.component.html',
  styleUrls: ['./card-flip-front.component.css']
})
export class CardFlipFrontComponent implements OnInit {
  constructor(private _el: ElementRef, private _renderer: Renderer) { }

  public get el(): ElementRef {
    return this._el;
  }

  public get renderer(): Renderer {
    return this._renderer;
  }

  ngOnInit() { }
}
&lt;ng-content&gt;&lt;/ng-content&gt;

更新:

好的,所以在阅读了一些类似的挑战和github issue here 之后,我尝试了以下操作。

<ng-template #frontTemplate>
  <ng-content select="card-flip-front"></ng-content>
</ng-template>

<ng-container *ngIf="isOpen == true" #front1>
  <ng-container *ngTemplateOutlet="frontTemplate"></ng-container>
</ng-container>

<ng-container *ngIf="isOpen == false" #front2>
  <ng-container *ngTemplateOutlet="frontTemplate"></ng-container>
</ng-container>

<ng-content select="card-flip-back"></ng-content>

基本上,我们可以通过将ng-content 放置在模板中并使用两个带有*ngIf 语句的ng-container 标记来解决单一投影问题,该语句将仅基于类属性显示模板的一个实例isOpen.

但这并不能解决整个问题,因为在任何给定时间只会呈现一个容器。因此,我无法获取“原始”的当前位置来确定在上述返回动画期间为“克隆”设置动画的位置。

【问题讨论】:

    标签: javascript angular


    【解决方案1】:

    我认为您可以在&lt;card-flip&gt; 模板中拥有一个中间组件&lt;card-flip-content&gt;,该模板是重复的,它接收&lt;card-flip&gt;&lt;ng-content&gt;

    类似:

    @Component(
       selector = 'card-flip',
       template = `
       <card-flip-content #theOne>
          <ng-content />
       </card-flip-content>
       <card-flip-content #theClone>
          <ng-content />
       </card-flip-content>
    `)
    

    然后根据需要将数据绑定到#theOne 和#theClone 并仅对#theClone 进行动画处理。 这种方式可以有@Input 和@Output,从而让一个组件的操作由父级解释,以便对另一个组件起作用。

    这行得通吗?

    【讨论】:

    • 感谢您的想法,但我想我可能会误解。在您提供的示例中,您使用了 ng-content 两次。只有第一个 ng-content 标签会包含投影的内容,所以主要问题仍然存在。
    • ng-content 的这种限制是预期的行为,并在此处详细讨论 github.com/angular/angular/issues/7795
    猜你喜欢
    • 2017-11-01
    • 1970-01-01
    • 2017-07-13
    • 2016-12-29
    • 2012-09-02
    • 2014-12-20
    • 2018-09-28
    相关资源
    最近更新 更多