Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

list view component in angular which will accept any data source

i wanted to create common list component like below

<ul>
  <li *ngFor="let item of items">{{item.text}}</li>
</ul>

with interface

export interface Items{
id:string
text:string
}

and in component.ts

@input items:any

now i wanted to reuse above component with any data source.

for example,

<app-list-view [items]="ProductList"></app-list-view>

& products contains.

this.ProductList =[PRODUCTSITEMS...]
    export interface Products{
    produceId
    productText
    }

may be <app-list-view [items]="OrderList"></app-list-view>

this.OrderList=[ORDERITEMS...]
export interface Orders{
    orderId
    orderText
    }

how can we use typescript generics or something like that here?, i do not want to use map function to create 2 more extra property on ProductList and OrderList.

what is best way to achieve above functionality.

like image 247
Var Avatar asked Sep 23 '18 19:09

Var


2 Answers

Nice question; it's good that you want to make this component reusable for different types of lists.

Unfortunately, a generic won't help you with how your code works, it would only help you keep track of types while writing code. However, what you want to do can be implemented with an additional input.

Instead of writing {{item.text}} in the template and setting .text in stone, what if the name of this key is also an input? Let's call it key. Then the template would look like this:

{{item[key]}}

Now we just need to make key an input in the class. We can give it a default value text so that, if you don't give the name of the key, it falls back to item.text.

@Input() key: string = 'text'

Now you can use your component like this:

<app-list-view
  [items]="productList"
  [key]="'productText'"
></app-list-view>

<app-list-view
  [items]="ordersList"
  [key]="'orderText'"
></app-list-view>

In both cases, the correct thing will be chosen: in the first case, it will be the same as writing {{item.productText}} and in the second it would be like saying {{item.orderText}}.

like image 158
Lazar Ljubenović Avatar answered Oct 15 '22 09:10

Lazar Ljubenović


If you want to be able to reuse your component with any datasource you will simply need to define a display template for your component. As a base example your component definition could look like :

For product :

<app-list-view [items]="ProductList">
   <ng-template #option let-data="data">{{data?.productText + ' - ' + data?.productId}}</ng-template>
</app-list-view>

For order :

<app-list-view [items]="OrderList">
   <ng-template #option let-data="data">{{data?.orderText}}</ng-template>
</app-list-view>

How do I use the template ?

Inside your component you will need to get the template by using the decorator @ContentChild. Here we select the template thanks to the #option

@ContentChild("option") optionTemplate: TemplateRef<any>;

Then in your component html you can now use this template :

<ul>
  <li *ngFor="let item of items">
     <ng-template [ngTemplateOutlet]="optionTemplate" [ngTemplateOutletContext]="{$implicit : item}"></ng-template>
  </li>
</ul>

Bonus : to make it more clear to use for the user you can create a (directive + component) to manage your template and make the component use look like this :

<app-list-view [items]="OrderList">
   <app-list-option *optionDef="let-data">{{data?.orderText}}</app-list-option>
</app-list-view>

The app-list-option component :

@Component({
  selector: 'app-list-option',
  template: '<ng-content></ng-content>'
})
export class AppListOption {
  constructor() {
  }
}

The *optionDef structural directive who will capture the template :

@Directive({
  selector: '[optionDef]',
})
export class AppListOptionDef {
  constructor(public template: TemplateRef<any>) {
  }
}

To finish you just need to change the selector in your component :

@ContentChild(AppListOptionDef ) optionDef: AppListOptionDef;

And use optionDef.template in the HTML :

<ng-template [ngTemplateOutlet]="optionDef.template" [ngTemplateOutletContext]="{$implicit : item }"></ng-template>

For more information

You will find here some of my code working on the way I described

Reusable "select" code : https://github.com/xrobert35/asi-ngtools/blob/master/src/components/asi-select/asi-select.component.ts

The bonus part : https://github.com/xrobert35/asi-ngtools/blob/master/src/components/common/asi-component-template.ts

Working exemple : https://ng-tools.asi.fr/views/showroom/asi-ngtools/components/asi-select

like image 21
xrobert35 Avatar answered Oct 15 '22 10:10

xrobert35