简体   繁体   English

Angular 2 ngFor中不同组件的列表

[英]List of different components in Angular 2 ngFor

I know there are many similar questions and almost all of them end with DynamicComponentLoader answer but still, I think use case described below is so simple and common (IMO) that solution with Angular 2 should be straight forward. 我知道有很多类似的问题,几乎所有问题都以DynamicComponentLoader答案结束,但我认为下面描述的用例是如此简单和常见(IMO),Angular 2的解决方案应该是直截了当的。

Sample use case 示例用例

I have an array of news items with property type describing what kind of item it is. 我有一系列新闻项目,其属性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: {} }
]

Each different type has different view and quite different logic behind it. 每种不同的类型都有不同的view ,背后有完全不同的逻辑。 In other words - each type has its own angular2 Component . 换句话说 - 每种type都有自己的angular2 Component

So abstract code what I try to achieve is: 所以我试图实现的抽象代码是:

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

Of course it will not work. 当然它不会起作用。

Possible solution #1 可能的解决方案#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>

I don't like this solution not only because it looks ugly and I will have to include this line every time I'll add new type but I wonder if this solution is good from performance perspective? 我不喜欢这个解决方案不仅因为它看起来很丑陋而且我每次添加新类型时都必须包含这一行但是我想知道这个解决方案从性能角度来看是否合适? I mean if I have 10,000 different types and only 3 items to display. 我的意思是,如果我有10,000种不同的类型,只显示3个项目。 So angular2 will have to remove from DOM 9,999 tags and leave only one for each of 3 items (3 * 9999 remove operations). 因此angular2必须从DOM中移除9,999个标签,并且每个3个项目只留下一个(3 * 9999删除操作)。

Possible solution #2 可能的解决方案#2

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

At the moment I don't remember how exactly DynamicComponentLoader works (I have tried it in similar problem in angular2 alpha long time ago). 目前我不记得DynamicComponentLoader究竟是如何工作的(我已经在很久以前的angular2 alpha中尝试了类似的问题)。 But as I remember the code looks like hack for me.. For such common task?.. 但是我记得代码看起来像是我的hack ..对于这样的常见任务?...

Angular 1.x thinking Angular 1.x思考

I don't know what I do wrong, maybe the problem is that I still think in Angular 1 ? 我不知道我做错了什么,也许问题是我仍然认为在Angular 1 Using it I would use ngInclude or custom directive with template function. 使用它我会使用ngInclude或自定义指令与模板功能。

Guys, do you have other solutions how to do it? 伙计们,你有其他解决方案吗? Don't stick to my two potential solutions, maybe I need to think out of the box and solve this problem completely in different part of my application.. I'm confused. 不要坚持我的两个可能的解决方案,也许我需要开箱即用,并在我的应用程序的不同部分完全解决这个问题。我很困惑。 Thanks:) 谢谢:)

EDIT: One more real world example 编辑:一个更真实的例子

Let's say your task is to write Facebook with Angular 2. I think you would face same issue trying to display news feed. 假设您的任务是使用Angular 2编写Facebook。我认为您在尝试显示新闻源时会遇到同样的问题。 Each news feed item has it's type ( text , event , ads ,.. ) 每个新闻Feed都有它的类型( texteventads ......)

This is my solution: 这是我的解决方案:

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
  }

}

In your NgFor loop: 在你的NgFor循环中:

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

I'd write another component, say item-flex : 我会写另一个组件,比如item-flex

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

And item-flex could use either ngSwitch : 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>

or the "ugly ifs" (this way you can even get rid of the external tag/div/span that is present in ngSwitch solution): 或者“丑陋的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>

I guess you could use "ngComponentOutlet" that came with Angular 4 which creates component dynamically based on the value passed. 我想你可以使用Angular 4附带的“ngComponentOutlet”,它根据传递的值动态创建组件。 I havent tested the code though. 我没有测试过代码。

@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>
  `,
})

please refer url for more details : https://netbasal.com/a-taste-from-angular-version-4-50be1c4f3550 请参阅网址了解更多详情: https//netbasal.com/a-taste-from-angular-version-4-50be1c4f3550

My first thought would be to create a directive and use the Renderer class to add the appropriate component conditionally. 我的第一个想法是创建一个指令并使用Renderer类有条件地添加适当的组件。

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

Directive 指示

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 ...
    }
}

You can use more @Inputs to pass in parameters and attach them using other Renderer methods. 您可以使用更多@Inputs传递参数并使用其他Renderer方法附加它们。

This keeps the view very simple and will not not load modules for items whose tyoes ar not required. 这使视图非常简单,并且不会为不需要tyoe的项加载模块。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM