typescript Angular 2中模板内的类型转换

eh57zj3b  于 2023-04-13  发布在  TypeScript
关注(0)|答案(9)|浏览(103)

我正在做一个Angular项目(Angular 4.0.0),我在绑定一个抽象类的属性到ngModel时遇到了麻烦,因为我首先需要将它转换为它实际上是的具体类,以便访问该属性。
也就是说,我有一个AbstractEvent类,它有一个具体的实现Event,它有一个布尔属性'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上的双向绑定。
如果任何人有任何方法来解决这个问题或执行类型铸造角模板正确,我会非常感激!

x8diyxa7

x8diyxa71#

如果你不关心类型控制。
Angular8及更高版本

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

来自官方文件:https://angular.io/guide/template-typecheck#disabling-type-checking-using-any

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

zpjtge222#

这是不可能的,因为Event不能从模板中引用。
(模板绑定表达式中也不支持as)您需要首先使其可用:

class MyComponent {
  EventType = Event;

那这个应该能行

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

更新

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

然后用它作为

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

hyrbngr73#

这个管道可以用来从各种输入中获取类型。它可以很好地与类、命名类型/接口和原语一起工作。

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: {{ (propString | as : 123).toFixed(2) }}
</td>

需要严格的模板和常春藤。

yrwegjxp

yrwegjxp4#

如前所述,使用准系统方法调用会对性能产生影响。
一个更好的方法是使用管道,你可以两全其美。只需定义一个Cast管道:

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

然后在模板中,当需要转换时使用event | cast
这样,更改检测保持高效,并且键入是安全的(当然,假定所请求的类型更改是合理的)。
不幸的是,由于name属性的存在,我没有找到一种方法来实现这种泛型,因此必须为每种类型定义一个新的管道。

e1xvtsh3

e1xvtsh35#

*使用 TypeSafe 泛型answer
*smnbbrv answer启发,当没有地方可以推断类型时,将类型显式传递为可选参数。

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

 /**
  * Cast super type into type using generics
  * Return Type obtained by optional @param type OR assignment type.
  */

 @Pipe({ name: 'cast' })
 export class CastPipe implements PipeTransform {
     /**
      * Cast (S: SuperType) into (T: Type) using @Generics.
      * @param value (S: SuperType) obtained from input type.
      * @optional @param type (T CastingType)
      * type?: { new (): T }
      * type?: new () => T
      */
     transform<S, T extends S>(value: S, type?: new () => T): T {
         return <T>value;
     }
 }

用法:

  • 模板.html*
<input
     type="checkbox"
     *ngIf="event.end"
     [(ngModel)]="(event | cast: Event).acknowledged"
     [disabled]="(event | cast: Event).acknowledged"
 />
  • component.ts*
export abstract class AbstractEvent {
     end: boolean;
 }
 export class Event extends AbstractEvent {
     acknowledged: boolean;
 }

 export class MyComponent{
     event: AbstractEvent;
     Event = Event;
 }
pu82cl6c

pu82cl6c6#

要扩展@smnbbrv的答案,可以对接口使用类似的语法,如下所示:

@Pipe({ name: 'as', pure: true })
export class AsPipe implements PipeTransform {
  transform<T>(input: unknown, baseItem: T | undefined): T {
    return (input as unknown) as T;
  }
}

这需要我们提供一个正确类型的“baseItem”。然而,我们不需要实际创建项,我们只需要声明它(因为项可以是未定义的)。这意味着我们可以在我们的类中创建一个建议类型的变量,如下所示:

export interface Person{
  name: string;
  age: number;
}

export class MyComponent {
  Person: Person;
}

注意,我们没有给baseItem赋值,我们只是指定它的类型,如果你启用了strictPropertyInitialization,你需要给你的baseItem添加一个非空Assert

export class MyComponent {
  Person!: Person;
}

然后可以在模板中按如下所示使用该属性:

<td mat-cell *matCellDef="let row">
  {{ (row | as : Person).name }}
</td>
u0sqgete

u0sqgete7#

还可以创建返回类型 predicate 的函数。
app.component.html

<some-component *ngIf="isFoo(foo)" [foo]="foo"></some-component>

app.component.ts

isFoo(value: Foo | Bar): value is Foo {
    return value === 'Foo';
}

这将把模板变量foo强制转换为Foo类型,并将禁止任何关于联合类型的strictTemplate错误。

daupos2t

daupos2t8#

免责声明!我是ng-as Angular库的作者,该库具有用于类型转换模板变量的管道和指令。
定向铸造,例如:

import { Component } from '@angular/core';

// your interface, but also work with any typescript type (class, type, etc.)
interface Person {
  name: string;
}

@Component({
  selector: 'app-root',
  template: `
  <ng-container *ngTemplateOutlet="personTemplate; context: {$implicit: person}"></ng-container>
  <ng-template #personTemplate [ngAs]="Person" let-person>
    <span>Hello {{ person.name }}!</span>
  </ng-template>
  `,
})
export class AppComponent {
  // NOTE: If you have "strictPropertyInitialization" enabled, 
  // you will need to add a non-null assertion (!)
  public Person!: Person; // publish your interface into html template
  person: Person = { name: 'Simone' }; // the data
}

用管道铸造,例如:

import { Component } from '@angular/core';

// your interface, but also work with any typescript type (class, type, etc.)
interface Person {
  name: string;
}

@Component({
  selector: 'app-root',
  template: `
  <ng-container *ngTemplateOutlet="personTemplate; context: {$implicit: person}"></ng-container>
  <ng-template #personTemplate let-person>
    <span>Hello {{ (person | as: Person).name }}!</span>
  </ng-template>
  `,
})
export class AppComponent {
  // NOTE: If you have "strictPropertyInitialization" enabled, 
  // you will need to add a non-null assertion (!)
  public Person!: Person; // publish your interface into html template
  person: Person = { name: 'Simone' }; // the data
}

管道来源:

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

@Pipe({ name: 'as', pure: true })
export class NgAsPipe implements PipeTransform {
  // eslint-disable-next-line no-unused-vars
  transform<T>(input: unknown, baseItem: T | undefined): T {
    return input as unknown as T;
  }
}

指令来源:

import { Directive, Input } from "@angular/core";

interface NgAsContext<T> {
  ngLet: T;
  $implicit: T;
}

@Directive({ selector: '[ngAs]' })
export class NgAsDirective<T> {
  @Input() ngAs!: T;

  static ngTemplateContextGuard<T>(dir: NgAsDirective<T>, ctx: any): ctx is NgAsContext<Exclude<T, false | 0 | '' | null | undefined>> {
      return true;
  }
}

更多信息:https://www.npmjs.com/package/ng-as

x6492ojm

x6492ojm9#

Angular仍然不支持这一点。你可以创建一个自定义管道或一个函数来进行类型转换。
或者您可以使用以下语法将其转换为'any':$any()
示例:

{{$any(person).address.street}}

参考文件:https://angular.io/guide/template-typecheck

相关问题