Est-il possible de déclarer un composant de type générique dans Angular 4?
Le code suivant provoque des erreurs de génération:
export class MyGenericComponent<T> implements OnInit {
@Input() data: BehaviorSubject<T[]>;
//...
}
L'erreur lors de l'exécution de ng serve
Est:
ERROR in C:/.../my-generic.module.ts (5,10): Module '"C:/.../my-generic.component"' has no exported member 'MyGenericComponent'.
Exemple:
L'exemple suivant est une tentative d'implémentation d'une table de données générique où @Input() data
passe d'un composant "appelant ce composant" à un autre. La question est: BehaviorSubject<any[]>
Pourrait-il être changé en BehaviorSubject<T[]>
Où T
serait le type générique transmis au composant?
@Component({
selector: 'my-data-list',
templateUrl: './data-list.component.html',
styleUrls: ['./data-list.component.css']
})
export class DataListComponent implements OnInit {
@Input() data: BehaviorSubject<any[]>;
@Output() onLoaded = new EventEmitter<boolean>();
private tableDataBase : TableDataBase = new TableDataBase();
private dataSource : TableDataSource | null;
constructor() { }
ngOnInit() {
this.tableDataBase.dataChange = this.data;
this.dataSource = new TableDataSource(this.tableDataBase);
this.onLoaded.emit(true);
}
}
class TableDataBase {
dataChange: BehaviorSubject<any[]> = new BehaviorSubject<any[]>([]);
get data(): any[] {
return this.dataChange.value;
}
}
class TableDataSource extends DataSource<any> {
constructor(private tableDataBase: TableDataBase) {
super();
}
connect(): Observable<any[]> {
return Observable.of(this.tableDataBase.data);
}
disconnect() {}
}
Vous pouvez également accéder au paramètre Type via ViewChild comme ceci:
export class Bazz {
name: string;
constructor(name: string) {
this.name = name;
}
}
@Component({
selector: 'app-foo',
template: `<div>{{bazz?.name}}</div>`,
exportAs: 'appFoo'
})
export class FooComponent<T> {
constructor() {}
private _bazz: T;
set bazz(b: T) {
this._bazz = b;
}
get bazz(): T {
return this._bazz;
}
}
@Component({
selector: 'app-bar',
template: `<app-foo #appFoo></app-foo>`,
styleUrls: ['./foo.component.scss'],
})
export class BarComponent<T> implements OnInit {
@ViewChild('appFoo') appFoo: FooComponent<Bazz>;
constructor() {}
ngOnInit() {
this.appFoo.bazz = new Bazz('bizzle');
console.log(this.appFoo.bazz);
}
}
Vous pouvez le déclarer, mais ne pouvez pas l'utiliser directement. Vous pouvez faire quelque chose comme ça:
export abstract class Form<T> implements OnInit, OnChanges {
someMethod() { throw 'Dont use directly' }
otherMethod() { return 'Works!'; }
// Note that below will cause compilation error
// TypeError: Object prototype may only be an Object or null: undefined
// You cannot use protected in this usecase
protected anotherMethod() { }
}
@Component({})
export class ModelOneForm extends Form<ModelOne> {
someMethod() { return this.otherMethod(); }
}
Vous pouvez envisager de cette façon. Créez une interface pour les données, comme suit:
interface ListItem {
info: string;
...
}
Transformez les données que vous souhaitez répertorier pour qu'elles soient conformes à l'interface et peuvent ainsi être interprétées par le ListDataComponent. Votre ListDataComponent peut alors répertorier les données en fonction des propriétés de l'interface.
import { Component, OnInit, Input } from '@angular/core';
@Component({
selector: 'data-list',
templateUrl: './data-list.component.html',
styleUrls: ['./data-list.component.scss']
})
export class DataListComponent implements OnInit {
@Input() public items: ListItem[];
constructor() {
}
ngOnInit() {
}
}
Je recommanderais de créer un composant de liste parent avec plusieurs composants enfants pour chaque type de données affichées, puis utilisez [ngSwitch]
et *ngSwitchCase
pour déterminer les éléments à afficher.
@Component({
selector: 'app-list',
template: `
<ng-container *ngFor="let item in list$ | async" [ngSwitch]="item.type">
<app-list-item-one [item]="item" *ngSwitchCase="listItemType.One"></app-list-item-one>
<app-list-item-two [item]="item" *ngSwitchCase="listItemType.Two"></app-list-item-two>
</ng-container>
`
})
export class ListComponent extends OnInit {
list$: Observable<ListItem[]>
constructor(
private listApi: ListApiService
) { }
ngOnInit() {
this.list$ = this.listApi.getList(...)
}
}
@Component({
selector: 'app-list-item-one',
template: `
{{ item.aProperty }}
`
})
export class ListItemOneComponent {
@Input() item: ListItemOne
}
@Component({
selector: 'app-list-item-two',
template: `
{{ item.bProperty }}
`
})
export class ListItemTwoComponent {
@Input() item: ListItemTwo
}
export class ListItem {
id: string
}
export class ListItemOne {
aProperty: string
}
export class ListItemTwo {
bProperty: string
}