简体   繁体   中英

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.

Sample use case

I have an array of news items with property type describing what kind of item it is.

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. In other words - each type has its own 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

<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. So angular2 will have to remove from DOM 9,999 tags and leave only one for each of 3 items (3 * 9999 remove operations).

Possible solution #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). But as I remember the code looks like hack for me.. For such common task?..

Angular 1.x thinking

I don't know what I do wrong, maybe the problem is that I still think in Angular 1 ? Using it I would use ngInclude or custom directive with template function.

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. Each news feed item has it's type ( text , event , ads ,.. )

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:

<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]="item" *ngFor="let item of items"></item-flex>

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

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

My first thought would be to create a directive and use the Renderer class to add the appropriate component conditionally.

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

This keeps the view very simple and will not not load modules for items whose tyoes ar not required.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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