TypeScriptのジェネリック型でAngularコンポーネントをレベルアップ!汎用性と型安全性を手に入れる
Angular でジェネリック型を使ってコンポーネントを宣言する方法
ジェネリック型コンポーネントは、TypeScript のジェネリック型と同様に機能します。コンポーネントのテンプレートとロジックに 型パラメーター を定義することで、コンポーネントが処理するデータの種類を指定できます。
利点
ジェネリック型コンポーネントを使用する利点は次のとおりです。
- 型安全性
コンパイラは、コンポーネントが使用しているデータ型を検証し、エラーを防ぐことができます。 - 柔軟性
コンポーネントがさまざまな種類のデータを処理できるため、より汎用性が高くなります。 - コードの再利用性
同じロジックを共有するコンポーネントを複数作成する必要がなくなり、コードの重複が削減されます。
作成方法
ジェネリック型コンポーネントを作成するには、以下の手順に従います。
- 型パラメーターを宣言する
コンポーネントクラスの<
と>
の間に、コンポーネントが処理するデータ型を表す型パラメーターを宣言します。
<T>(
// コンポーネントのプロパティとメソッドを定義
)
- 型パラメーターを使用する
コンポーネントのテンプレートとロジック内で、型パラメーターを使用して、コンポーネントが処理しているデータ型にアクセスできます。
<p>Data: {{ data }}</p>
export class MyComponent<T>() {
data: T;
constructor() {
this.data = {} as T;
}
}
- コンポーネントを使用する
コンポーネントを使用する際には、実際のデータ型を<
と>
の間に指定する必要があります。
<my-component [data]="myData"></my-component>
例
次の例は、User
インターフェースを定義し、そのインターフェースを使用して User
データを処理するジェネリック型コンポーネントを作成する方法を示します。
interface User {
name: string;
email: string;
}
export class UserComponent<T extends User>() {
data: T;
constructor() {
this.data = {} as T;
}
showData() {
console.log(this.data.name + " - " + this.data.email);
}
}
このコンポーネントは次のように使用できます。
<my-component [data]="userData"></my-component>
ここで、userData
は User
インターフェースを実装したオブジェクトです。
シナリオ
この例では、Hero
と Villain
という 2 つのインターフェースを定義し、それぞれ異なるプロパティを持つオブジェクトを表します。次に、これらのインターフェースを使用して、HeroComponent
と VillainComponent
という 2 つのジェネリック型コンポーネントを作成します。これらのコンポーネントは、それぞれ Hero
または Villain
オブジェクトを受け取り、そのオブジェクトのプロパティを表示します。
最後に、GenericListComponent
というジェネリック型ディレクティブを作成します。このディレクティブは、Hero
または Villain
オブジェクトの配列を受け取り、その配列内の各オブジェクトを HeroComponent
または VillainComponent
インスタンスでレンダリングします。
コード
interface Hero {
id: number;
name: string;
power: string;
}
interface Villain {
id: number;
name: string;
nemesis: string;
}
@Component<T extends Hero | Villain>({
selector: 'app-hero-villain',
templateUrl: './hero-villain.component.html',
})
export class HeroVillainComponent<T> {
@Input() data: T;
constructor() {}
}
@Directive({
selector: '[appGenericList]',
})
export class GenericListComponent<T extends Hero | Villain> {
@Input() data: T[];
constructor(private templateRef: TemplateRef<any>) {}
ngOnInit() {
const viewContainerRef = this.viewContainerRef;
this.data.forEach((item) => {
const component = viewContainerRef.createComponent<
HeroVillainComponent<T>
>(this.templateRef);
component.instance.data = item;
});
}
}
<div class="hero-villain">
<h2>{{ data.name }}</h2>
<p>{{ data.power || data.nemesis }}</p>
</div>
<ul appGenericList [data]="heroes">
<li *ngFor="let hero of heroes"></li>
</ul>
<ul appGenericList [data]="villains">
<li *ngFor="let villain of villains"></li>
</ul>
説明
appGenericList
ディレクティブは、GenericListComponent
によってレンダリングされるデータリストを指定するために使用されます。app-hero-villain
テンプレートは、HeroComponent
またはVillainComponent
によってレンダリングされるコンテンツを定義します。GenericListComponent
は、Hero
またはVillain
オブジェクトの配列を受け取り、その配列内の各オブジェクトをHeroComponent
またはVillainComponent
インスタンスでレンダリングするジェネリック型ディレクティブです。HeroComponent
とVillainComponent
は、それぞれHero
またはVillain
オブジェクトを受け取り、そのオブジェクトのプロパティを表示するジェネリック型コンポーネントです。
高度な型
ジェネリック型コンポーネントを作成するもう 1 つの方法は、高度な型を使用することです。これにより、コンポーネントが処理するデータ型についてより多くの制御を行うことができます。
export class MyComponent<T extends { name: string; age: number }>() {
// ...
}
この例では、MyComponent
コンポーネントは、name
と age
というプロパティを持つオブジェクトのみを処理できます。
型パラメーターの制約
型パラメーターに 制約 を追加することで、コンポーネントが処理できるデータの種類をさらに制限できます。
export class MyComponent<T extends { name: string } & { age: number }>() {
// ...
}
この例では、MyComponent
コンポーネントは、name
と age
というプロパティを持つオブジェクトのみを処理できます。さらに、これらのプロパティは、それぞれ string
型と number
型である必要があります。
型パラメーターのデフォルト値
型パラメーターに デフォルト値 を指定することで、コンポーネントが処理するデータ型のデフォルト値を定義できます。
export class MyComponent<T = string>() {
data: T;
constructor() {
this.data = '' as T;
}
}
この例では、MyComponent
コンポーネントは、デフォルトで string
型のデータを処理します。ただし、data
プロパティに string
以外の値を割り当てることもできます。
型推論
Angular は、ジェネリック型コンポーネントを使用する場合でも、型推論 をサポートしています。つまり、コンポーネントで使用されているデータ型を明示的に指定する必要はありません。
<my-component [data]="myData"></my-component>
この例では、myData
変数が string
型の値である場合、MyComponent
コンポーネントは string
型のデータを処理します。
angular typescript angular-components