List of various components in Angular 2 ngFor

I know that there are many similar questions, and almost all of them end with the DynamicComponentLoader responder, but still, I think that the use case described below is so simple and common (IMO) that the solution with Angular 2 should be straightforward.

Usage example

I have an array of news with a type property that describes what this element 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 other type has a different view and a completely different logic. In other words, each type has its own angular2 Component .

Thus, the abstract code I'm trying to achieve is:

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

Of course, this 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 have to include this line every time I add a new type, but I wonder if this solution is good in terms of performance? I mean, if I have 10,000 different types and only 3 elements to display. Thus, angular2 will have to be removed from the DOM 9,999 tags and only one should be left for each of the 3 elements (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 DynamicComponentLoader works (I tried this for a long time in a similar problem in angular2 alpha). But, as I recall, the code looks like a hack to me .. For such a general task? ..

Angular 1.x thinking

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

Do you guys have other solutions how to do this? Don't stick to my two possible solutions, maybe I need to think out of the box and solve this problem completely in different parts of my application. I'm confused. Thanks:)

EDIT: another example in the real world

Let's say your task is to write Facebook using Angular 2. I think you would run into the same problem trying to display a news feed. Each news item has a type ( text , event , ads , ..)

+7
javascript angularjs angular
source share
4 answers

I assume that you can use the "ngComponentOutlet" that comes with Angular 4, which dynamically creates a component based on the passed value. However, I have not tested the code.

 @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 to url for more information: https://netbasal.com/a-taste-from-angular-version-4-50be1c4f3550

+1
source share

This is my decision:

 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> 
+4
source share

I would write another component, say item-flex :

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

And item-flex can 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 ugly ifs (this way you can even get rid of the external / div / span tag that is present in the ngSwitch solution):

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

My first thought would be to create a directive and use the Renderer class to add the corresponding 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 parameters and attach them using other Renderer methods.

This simplifies the presentation and does not load modules for elements that are not required.

+2
source share

All Articles