【问题标题】:Type casting within a template in Angular 2在 Angular 2 的模板中进行类型转换
【发布时间】:2018-02-08 09:55:45
【问题描述】:

我正在开发一个 Angular 项目 (Angular 4.0.0),我在将抽象类的属性绑定到 ngModel 时遇到问题,因为我首先需要将其转换为实际的具体类才能访问属性。

即我有一个 AbstractEvent 类,它有一个具体的实现 Event,它有一个布尔属性“已确认”,我需要通过 ngModel 进行双向绑定以使用复选框进行设置。

我的 DOM 中有这个元素:

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

不幸的是,这引发了以下错误:

未捕获的错误:模板解析错误: Parser Error: Missing expected ) at column 8 in [(event as Event).acknowledged]

谷歌搜索似乎表明这可能是因为在模板中使用“as”时不支持使用它?虽然我不确定。

我也不知道如何在驱动模板的打字稿文件中为它编写一个函数,因为这会破坏我需要的 ngModel 上的双向绑定。

如果有人有办法解决这个问题或在角度模板中正确执行类型转换,我将不胜感激!

【问题讨论】:

    标签: angular typescript


    【解决方案1】:

    这是不可能的,因为无法从模板中引用 Event

    (模板绑定表达式也不支持as) 您需要先使其可用:

    class MyComponent {
      EventType = Event;
    

    那么这应该可以工作

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

    更新

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

    然后将其用作

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

    【讨论】:

    • 感谢您的快速回复,但我似乎仍然收到相同的错误:“未捕获的错误:模板解析错误:解析器错误:缺少预期的)在第 8 列 [(event as EventType).acknowledged ]"
    • 我更新了我的答案。它可能会对性能产生显着影响,因为模板中绑定的函数经常被调用。如果没有必要,我会尽量避免使用它。没有强制转换,你会得到错误吗?
    • 非常感谢它的工作。是的,我不太担心性能。目前正在研究我的一位同事编写的原型项目。将来我可能会尽量避免这种需要铸造的设计。再次非常感谢。
    • 在下面查看我的答案,了解管道解决方案,避免负面性能影响。
    • 考虑到变更检测会导致强制转换函数被多次调用,有没有其他方法可以在不直接在模板中使用函数的情况下达到相同的效果?我一直被告知要尽可能避免这种情况。
    【解决方案2】:

    如前所述,使用准系统方法调用会对性能产生影响。

    更好的方法是使用管道,您可以两全其美。只需定义一个 Cast 管道:

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

    然后在你的模板中,当你需要演员表时使用event | cast

    这样,更改检测保持高效,并且键入是安全的(假设请求的类型更改当然是合理的)。

    不幸的是,由于name 属性,我看不到这种通用的方法,因此您必须为每种类型定义一个新管道。

    【讨论】:

    【解决方案3】:

    如果你不关心类型控制。

    在 Angular 8 及更高版本中

    [(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;
    }
    

    【讨论】:

    • 这就像把垃圾藏在地毯下一样。它有效,但我们应该很少使用它。
    【解决方案4】:

    如果您使用类(不是接口!),您可以传递类以从中提取类型。

    import { Pipe, PipeTransform } from '@angular/core';
    
    @Pipe({
      name: 'as',
      pure: true,
    })
    export class AsPipe implements PipeTransform {
    
      transform<T>(value: any, clss: new (...args: any[]) => T): T {
        return value as T;
      }
    
    }
    

    clss 参数未使用,但其主要目标是:从构造函数中推断出类型。

    可以用作:

    class Event {
      prop: string;
    }
    
    export class MyComponent {
    
      MyClass = MyClass; // export class as value, that is visible in the template
    
    }
    
    <td mat-cell *matCellDef="let row">
      {{ (row | as : MyClass).prop }}
    </td>
    

    这不适用于接口,因为接口无法传递到模板中(在撰写本文时)。

    要使用接口,你可以将它包装到类中:

    class MyClass implements Partial<MyInterface> {}
    

    使用 Angular 11.1 测试并启用最新的 Ivy 语言服务。

    【讨论】:

    • 在我的情况下似乎不起作用,它说Identifier 'a' is not defined. 'T' does not contain such a member
    • 在启用“strictTemplates”的 Angular 12 中工作。
    • 检查我的完全泛型solution
    【解决方案5】:

    要扩展@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 添加一个非空断言

    export class MyComponent {
      Person!: Person;
    }
    

    这可以在您的模板中使用,如下所示:

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

    【讨论】:

      【解决方案6】:

      使用我的 TypeSafe 泛型 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;
          }
      }
      

      用法:

      template.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;
      }
      

      【讨论】:

        【解决方案7】:

        您还可以创建一个返回Type 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 错误。

        【讨论】:

        • 这似乎不适用于模板。
        猜你喜欢
        • 2014-02-08
        • 2016-02-13
        • 1970-01-01
        • 2015-06-27
        • 2012-10-05
        • 1970-01-01
        • 1970-01-01
        • 2017-07-28
        • 1970-01-01
        相关资源
        最近更新 更多