ウェブサイト検索

TypeScript - Angular 2のテンプレート内での型キャスト


私はAngularプロジェクト(Angular 4.0.0)に取り組んでおり、抽象クラスのプロパティをngModelにバインドするのに問題があります。そのプロパティにアクセスするためには、まずそれが実際にどの具体クラスであるかにキャストする必要があるからです。

つまり、私は AbstractEvent クラスを持っており、これには具体的な実装である Event クラスがあります。Event クラスには boolean 型のプロパティ 'acknowledged' があり、チェックボックスで設定するために ngModel を介して双方向バインディングが必要です。

私は現在、DOMにこの要素を持っています:

<input type="checkbox" *ngIf="event.end" [(ngModel)]="(event as Event).acknowledged" 
                                          [disabled]="(event as Event).acknowledged">

残念ながら、これは次のエラーを引き起こしています:

キャッチされていないエラー: テンプレート解析エラー: パーサーエラー: [(event as Event).acknowledged] の8列目で予期される ) がありません

グーグルで調べてみると、テンプレート内で 'as' を使うことはサポートされていないためかもしれない、ということのようです。ただし、確信はありません。

私はまた、テンプレートを操作している自分のTypeScriptファイルでそれのために関数を書くだけの方法も分かりません。なぜなら、これは私が必要とするngModelの双方向バインディングを壊してしまうからです。

もし誰かがこれを回避する方法や、Angularのテンプレートで型キャストを正しく行う方法を知っていれば、大変感謝します!

回答 #1

型の制御を気にしない場合。

Angular 8以上のバージョンでは

[(ngModel)]="$any(event).acknowledged"

公式ドキュメントより: https://angular.dev/tools/cli/template-typecheck

新しいドキュメント: https://angular.dev/tools/cli/template-typecheck

@Component({
  selector: 'my-component',
  template: '{{$any(person).addresss.street}}'
})
class MyComponent {
  person?: Person;
}

回答 #2

それは不可能です。なぜなら、Event はテンプレート内から参照できないからです。

(as はテンプレートバインディング式でもサポートされていません) まずそれを利用可能にする必要があります:

class MyComponent {
  EventType = Event;

それならこれでうまくいくはずです

[(ngModel)]="(event as EventType).acknowledged"

更新

class MyComponent {
  asEvent(val) : Event { return val; }

それからそれを〜として使う

[(ngModel)]="asEvent(event).acknowledged"

回答 #3

このパイプは、さまざまな入力から型を取得するために使用できます。クラス、名前付き型/インターフェース、プリミティブでもかなりうまく機能します。

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'as',
  pure: true,
})
export class AsPipe implements PipeTransform {

  transform<T>(value: any, _type: (new (...args: any[]) => T) | T): T {
    return value as T;
  }

}

_type 引数は使用されていませんが、主な目的に役立っています: 型はコンストラクタや変数から推論されます。

以下のように使用できます:

class ClassEvent {
  prop: string;
}

interface InterfaceEvent {
  prop: string;
}

export class MyComponent {

  MyClass = ClassEvent; // class constructor

  MyInterface: InterfaceEvent; // typed property

  propString: any; // primitive, string

  propNumber: any; // primitive, number

}
<td mat-cell *matCellDef="let row">
  Type from class constructor: {{ (row | as : MyClass).prop }}
  Type from interface: {{ (row | as : MyInterface).prop }}
  Type from primitive, string: {{ (propString | as : '').substr(1) }}
  Type from primitive, number: {{ (propNumber | as : 123).toFixed(2) }}
</td>

厳格なテンプレートとIvyを必要とします。

回答 #4

前述のように、ベアボーンのメソッド呼び出しを使用すると、パフォーマンスに影響があります。

より良い方法はパイプを使うことで、両方の利点を手に入れることができます。単に Cast パイプを定義してください:

@Pipe({
  name: 'cast',
  pure: true
})
export class CastPipe implements PipeTransform {  
  transform(value: any, args?: any): Event {
    return value;
  }
}

そして、テンプレート内でキャストが必要なときに、event | cast を使用します。

そうすることで、変更検出は効率的に保たれ、型付けは安全です(もちろん、要求された型変更が妥当である場合に限ります)。

残念ながら、name 属性のためにこれを汎用化する方法はないようですので、各タイプごとに新しいパイプを定義する必要があります。

関連記事