简体   繁体   中英

angular 4 generic list with various list item templates

I am trying to build up a generic component which displays a list of items with angular 4 and Material Design. The goal is, to have a list to which I only provide the child component like this:

<generic-list 
  title="My name list title"
  [items]="names" 
  (change)="handleNameChanged($event)">
    <some-child let-item="name"></some-child>
</generic-list>

@ViewChild(GenericListItem)
childItem: GenericListItem;

or this:

<generic-list ... [type]="simpleListItem"></generic-list>

@Input() type: Type<GenericListItem>;

export class GenericListItem {
  @Input() value: any;
}

So, any child component should extend from GenericListItem and would be able to display the properties of value like it wants to. Some example components I'm in need of:

@Component({
  selector: 'app-simple-list-item',
  template: '<p>{{value?.name}}</p>'
})
export class SimpleListItem extends GenericListItem {
}

@Component({
  selector: 'app-chechbox-list-item',
  template: '<md-checkbox [(ngModel)]="value?.enabled">{{value?.name}} &
    {{value?.email}}</md-checkbox>'
})
export class CheckboxListItem extends GenericListItem {
}

The generic-list component looks like this:

<md-list>
  <h2 md-subheader>{{title}}</h2>
  <md-list-item #outlet
    *ngFor="let item of items; let i = index"
    (click)="change.emit(item)">
    // in here should go the content for each item
  </md-list-item>
</md-list>

I've searched for ways to achieve this, however, none of the already present solutions are valid anymore or deprecated. Also the somewhat spare documentation on this topic does not help either...

I found this post which I tried to adapt, resulting in this code in generic-list :

export class GenericListComponent implements OnInit, AfterViewInit {
//inputs, outputs and other properties here
@ViewChildren('outlet', {read: ViewContainerRef})
outlets: QueryList<ViewContainerRef>;

ngOnInit() {
}

ngAfterViewInit() {
  console.log(this.outlets); // prints '3', since I've declared 3 items
  this.isViewInitialized = true;
  this.updateChildren();
}

updateChildren() {
  if (!this.isViewInitialized) {
    return;
  }
  this.outlets.forEach((outlet, index) => {
    const factory = this.cfr.resolveComponentFactory(this.type);
    const componentRef = outlet.createComponent(factory);
    componentRef.instance.value = this.items[index];
  });
}

which results in an error about ChangeDetection

Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined'. Current value: 'Hi1V'. It seems like the view has been created after its parent and its children have been dirty checked. Has it been created in a change detection hook ?

My test component:

<app-generic-list
  title="AppTitle"
  [items]="names"
  [type]="simpleListItem"
  (change)="handleItemChanged($event)"></app-generic-list>

export class AppComponent {
  simpleListItem = SimpleListItemComponent;
  names = [
    {value: 'Hi1V', desc: 'Hello1'},
    {value: 'Hi2V', desc: 'Hello2'},
    {value: 'Hi3V', desc: 'Hello3'}
  ];

  handleItemChanged(item: string) {
    console.log(item);
  }
}

It seems like I may be close to a solution already, but now I don't know at which point I need to instanciate the components. Also, will custom outputs in the child components work and can be called from outside of generic-list ?

Wouldn't https://github.com/elgervb/ngx-components/blob/master/src/app/list/list.component.ts be a better solution. You can create a list and supply a template to render the items. In the template you can use your own custom components

All in a very generic way.

An example of usage would be:

<evb-list [items]="items" [filterEnabled]="true">

<ng-template let-item>
  <md-checkbox [(ngModel)]="item?.enabled">{{item?.name}} &&
  {{value?.email}}</md-checkbox>
</ng-template>

Where item in ng-template will be each item in the list.

I think this component does everything you want in a generic way.

Hope this is any help

;-)

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