声明具有通用类型的组件
Declare a component with generic type
是否可以在 Angular 4 中声明具有通用类型的组件?
以下代码导致构建错误:
export class MyGenericComponent<T> implements OnInit {
@Input() data: BehaviorSubject<T[]>;
//...
}
执行ng serve
时的错误是:
ERROR in C:/.../my-generic.module.ts (5,10): Module '"C:/.../my-generic.component"' has no exported member 'MyGenericComponent'.
示例:
以下示例尝试实现通用数据 table,其中 @Input() data
从一个组件 'calling this component' 更改为另一个组件。
问题是 BehaviorSubject<any[]>
可以更改为 BehaviorSubject<T[]>
其中 T
将是传递给组件的通用类型吗?
@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() {}
}
可以声明,但不能直接使用。你可以这样做:
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(); }
}
你可以这样考虑。为数据创建一个接口,如下所示:
interface ListItem {
info: string;
...
}
将要列出的数据转换为符合接口,从而可以由 ListDataComponent 解释。然后你的 ListDataComponent 就可以根据界面中的属性列出数据了。
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() {
}
}
您还可以像这样通过 ViewChild 访问 Type 参数:
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);
}
}
我建议为显示的每种类型的数据创建一个包含多个子组件的父列表组件,然后使用 [ngSwitch]
和 *ngSwitchCase
来确定要显示的内容。
@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
}
我做了类似Jun711的事情。我创建了一个接口,然后我的组件使用该接口。然后我只是扩展另一个 类 的接口。我只是传入了一个扩展接口类型的数组。
export interface INameable {
name: string;
}
export interface IPerson extends INameable { title: string; }
export interface IManager extends INameable { Employees: IPerson[]; }
@Component({
selector: 'nameable',
templateUrl: './nameable.component.html',
styleUrls: ['./nameable.component.scss'],
})
export class NameableComponent implements OnInit {
@Input() names: INameable[] = [];
@Output() selectedNameChanged = new EventEmitter<INameable>();
constructor() {}
ngOnInit() {}
}
那么用法就很简单了:
<nameable [names]="PersonList" (selectedNameChanged)="personChangedHandler($event)"></nameable>
<nameable [names]="ManagerList" (selectedNameChanged)="mangagerChangedHandler($event)"></nameable>
缺点是包含组件必须确定完整类型,但优点是我的组件变得更加可重用,因为我遵循 Liskov 和 ISP。
是否可以在 Angular 4 中声明具有通用类型的组件?
以下代码导致构建错误:
export class MyGenericComponent<T> implements OnInit {
@Input() data: BehaviorSubject<T[]>;
//...
}
执行ng serve
时的错误是:
ERROR in C:/.../my-generic.module.ts (5,10): Module '"C:/.../my-generic.component"' has no exported member 'MyGenericComponent'.
示例:
以下示例尝试实现通用数据 table,其中 @Input() data
从一个组件 'calling this component' 更改为另一个组件。
问题是 BehaviorSubject<any[]>
可以更改为 BehaviorSubject<T[]>
其中 T
将是传递给组件的通用类型吗?
@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() {}
}
可以声明,但不能直接使用。你可以这样做:
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(); }
}
你可以这样考虑。为数据创建一个接口,如下所示:
interface ListItem {
info: string;
...
}
将要列出的数据转换为符合接口,从而可以由 ListDataComponent 解释。然后你的 ListDataComponent 就可以根据界面中的属性列出数据了。
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() {
}
}
您还可以像这样通过 ViewChild 访问 Type 参数:
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);
}
}
我建议为显示的每种类型的数据创建一个包含多个子组件的父列表组件,然后使用 [ngSwitch]
和 *ngSwitchCase
来确定要显示的内容。
@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
}
我做了类似Jun711的事情。我创建了一个接口,然后我的组件使用该接口。然后我只是扩展另一个 类 的接口。我只是传入了一个扩展接口类型的数组。
export interface INameable {
name: string;
}
export interface IPerson extends INameable { title: string; }
export interface IManager extends INameable { Employees: IPerson[]; }
@Component({
selector: 'nameable',
templateUrl: './nameable.component.html',
styleUrls: ['./nameable.component.scss'],
})
export class NameableComponent implements OnInit {
@Input() names: INameable[] = [];
@Output() selectedNameChanged = new EventEmitter<INameable>();
constructor() {}
ngOnInit() {}
}
那么用法就很简单了:
<nameable [names]="PersonList" (selectedNameChanged)="personChangedHandler($event)"></nameable>
<nameable [names]="ManagerList" (selectedNameChanged)="mangagerChangedHandler($event)"></nameable>
缺点是包含组件必须确定完整类型,但优点是我的组件变得更加可重用,因为我遵循 Liskov 和 ISP。