【问题标题】:List of different components in Angular 2 ngForAngular 2 ngFor 中不同组件的列表
【发布时间】:2016-11-13 20:22:27
【问题描述】:

我知道有很多类似的问题,几乎所有问题都以 DynamicComponentLoader 的答案结束,但我认为下面描述的用例非常简单和常见 (IMO),Angular 2 的解决方案应该是直截了当的。

示例用例

我有一组新闻项目,其属性为 type,描述它是什么类型的项目。

var items = [
  { id: 1, type: 'text', data: {} },
  { id: 2, type: 'text', data: {} },
  { id: 3, type: 'text-two-columns', data: {} },
  { id: 4, type: 'image-text', data: {} },
  { id: 5, type: 'image', data: {} },
  { id: 6, type: 'twitter', data: {} },
  { id: 7, type: 'text', data: {} }
]

每种不同的类型都有不同的view,背后的逻辑也大不相同。换句话说 - 每个type 都有自己的 angular2 Component

所以我尝试实现的抽象代码是:

<div *ngFor="let item of items">
   <item-{{item.type}} [data]="item.data"></item-{{item.type}}>
</div>

当然不行。

可能的解决方案#1

<div *ngFor="let item of items">
   <item-text *ngIf="item.type === 'text'" [data]="item.data"></item-text>
   <item-image *ngIf="item.type === 'image'" [data]="item.data"></item-image>
   ...
</div>

我不喜欢这个解决方案,不仅因为它看起来很丑,而且我每次添加新类型时都必须包含这一行,而且我想知道这个解决方案从性能角度来看是否很好?我的意思是,如果我有 10,000 种不同的类型并且只有 3 项要显示。因此 angular2 必须从 DOM 中删除 9,999 个标签,并为 3 个项目中的每一个保留一个(3 * 9999 个删除操作)。

可能的解决方案#2

<div *ngFor="let item of items">
   <dynamic-component-loader [item]="item"></dynamic-component-loader>
</div>

目前我不记得DynamicComponentLoader 究竟是如何工作的(我很久以前在 angular2 alpha 中尝试过类似的问题)。但我记得代码对我来说看起来像hack.. 对于这样的常见任务?..

Angular 1.x 思考

我不知道我做错了什么,也许问题是我还在Angular 1中思考?使用它我会使用ngInclude 或带有模板函数的自定义指令。

伙计们,你们有其他解决方案吗?不要坚持我的两个潜在解决方案,也许我需要开箱即用,并在我的应用程序的不同部分完全解决这个问题。我很困惑。谢谢:)

编辑:另一个真实世界的例子

假设您的任务是使用 Angular 2 编写 Facebook。我认为您在尝试显示新闻提要时会遇到同样的问题。每个新闻提要项都有其类型(texteventads、..)

【问题讨论】:

  • 如果您的组件数量有限,请继续尝试 (`*ngIf*)
  • 是的,ngIf对我来说很好看,这是最干净的方式,不知道你为什么说它丑。如果你想看到丑陋的东西,看看 DynamicComponentLoader / ComponentFactoryResolver
  • @tibbus 好答案:D 我看到了 DCL。我没有看到 ngIf 的解决方案非常难看,但我认为有更聪明的方法,我认为我认为解决这个任务有些错误。但是,如果你们说没关系-我完全可以接受。实际上会有多达 10 种不同的类型。
  • 你考虑过ngSwitch吗?它与您的解决方案 #1 一样难看,减去循环。

标签: javascript angular


【解决方案1】:

这是我的解决方案:

import { Component, OnInit, ViewContainerRef, TemplateRef, ComponentFactoryResolver, Input } from '@angular/core';

@Component({
  selector: 'item',
  template: '',
  styleUrls: ['./item.component.scss']
})
export class ItemComponent implements OnInit {
  @Input() type: string;
  @Input() data: any;

  constructor(
    private viewContainerRef: ViewContainerRef,
    private componentFactoryResolver: ComponentFactoryResolver,
    private componentLookupService: YourComponentLookUpService
  ) { }

  ngOnInit() {
    const component = this.componentLookupService.findByType(type);
    const componentFactory = this.componentFactoryResolver.resolveComponentFactory(component);
    // Look at the https://angular.io/docs/ts/latest/api/core/index/ViewContainerRef-class.html#!#createComponent-anchor for more information about how to use DI... in the createComponent function.
    const componentRef =this.viewContainerRef.createComponent(componentFactory);
    // Or you can then access the newly created component here: this.componentRef.instance
  }

}

在你的 NgFor 循环中:

<div *ngFor="let item of items">
   <item [type]="item.type" [data]="item.data"></item>
</div>

【讨论】:

    【解决方案2】:

    我猜你可以使用 Angular 4 附带的“ngComponentOutlet”,它根据传递的值动态创建组件。不过我还没有测试过代码。

    @Component({
        selector: 'my-app',
        template: `
        <h1>Angular version 4</h1>
        <div *ngFor="let <component name> of <list of component name>">
            <ng-container *ngComponentOutlet="<component name>">enter code here</ng-container>
        </div>
      `,
    })
    

    详情请参考网址:https://netbasal.com/a-taste-from-angular-version-4-50be1c4f3550

    【讨论】:

      【解决方案3】:

      我会写另一个组件,比如item-flex

      <item-flex [item]="item" *ngFor="let item of items"></item-flex>
      

      item-flex 可以使用ngSwitch

      <div [ngSwitch]="item.type">
          <item-text *ngSwitchCase="'text'" [data]="item.data"></item-text>
          <item-image *ngSwitchCase="'image'" [data]="item.data"></item-image>
          <span *ngSwitchDefault >UNKNOWN TYPE:{{item.type}}</span>
      </div>
      

      或“丑陋的 ifs”(这样您甚至可以摆脱 ngSwitch 解决方案中存在的外部标签/div/span):

      <item-text *ngIf="item.type=='text'" [data]="item.data"></item-text>
      <item-image *ngIf="item.type=='image'" [data]="item.data"></item-image>
      

      【讨论】:

        【解决方案4】:

        我的第一个想法是创建一个指令并使用Renderer class 有条件地添加适当的组件。

        <div app-item [type]="item.type" [data]="item.data"></div>
        

        指令

        import { Directive, ElementRef, Input, Renderer,  OnInit } from '@angular/core';
        
        @Directive({
          selector: '[app-item]'
        })
        export class ItemDirective implements OnInit {
            @Input('type') type: string;
            @Input('data') data: any[];
            constructor(private el: ElementRef, private r: Renderer) {  }
        
            ngOnInit(): void {
                switch(this.type){
                case: 'text'
                    let self = this.r.createElement( this.el.nativeElement, 'item-text' );
                    this.r.setElementAttribute(self, 'data', 'this.data')
                    break;
                case: 'image');
                    let self = this.r.createElement( this.el.nativeElement, 'item-image'
                    this.r.setElementAttribute(self, 'data', 'this.data')
        
                    break;
                // ... so on ...
            }
        }
        

        您可以使用更多@Inputs 来传入参数并使用其他Renderer 方法附加它们。

        这使视图非常简单,并且不会为不需要 tyo 的项目加载模块。

        【讨论】:

        • 它将产生 而不是 item-text 的编译模板
        猜你喜欢
        • 2018-10-09
        • 1970-01-01
        • 1970-01-01
        • 2017-09-20
        • 2018-09-02
        • 1970-01-01
        • 2017-11-17
        • 2017-05-07
        • 2016-09-26
        相关资源
        最近更新 更多