Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 796
Arūnas Smaliukas Avatar asked Nov 13 '16 20:11

Arūnas Smaliukas


People also ask

Is there a list in Angular?

At its core the Angular list component allows you to easily display a vertical list of items. The default styling of the items is done according to the single-line list specification as per the Material Design guidelines.

Why * is used in ngFor?

In *ngFor the * is a shorthand for using the new angular template syntax with a template tag, this is also called structural Directive.It is helpful to know that * is just a shorthand to explicitly defining the data bindings on a template tag.

Does angular 2 provide nested components?

Angular 2 still does not provide nested components.


2 Answers

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>
like image 199
Tuong Le Avatar answered Oct 04 '22 01:10

Tuong Le


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

like image 37
Anoop Isaac Avatar answered Oct 04 '22 01:10

Anoop Isaac