Type casting within a template in Angular 2

I'm working on an Angular project (Angular 4.0.0) and I'm having trouble binding a property of an abstract class to ngModel because I first need to cast it as the concrete class it actually is in order to access the property.

i.e. I have an AbstractEvent class this has a a concrete implementation Event which has a boolean property 'acknowledged' which I need a two way binding via ngModel to set with a checkbox.

I currently have this element in my DOM:

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

Unfortunately this is throwing the following error:

Uncaught Error: Template parse errors: Parser Error: Missing expected ) at column 8 in [(event as Event).acknowledged]

Googling around seemed to suggest this might be because using 'as' is not supported when using it inside a template? Although I'm not certain about this.

I also can't work out how to just write a function for it in my typescript file driving the template because this would break the two way binding on ngModel that I require.

If anyone has any way to get around this or perform type casting in angular templates correctly I would be very appreciative!


If you don't care about type control.

In Angular 8 and higher versions

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

From Offical Document: https://angular.io/guide/template-typecheck#disabling-type-checking-using-any

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

That's not possible because Event can't be referenced from within the template.

(as is also not supported in template binding expressions) You need to make it available first:

class MyComponent {
  EventType = Event;

then this should work

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

update

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

then use it as

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

As mentioned, using a barebone method call will have performance impact.

A better approach is to use a pipe, and you have best of both worlds. Just define a Cast pipe:

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

and then in your template, use event | cast when you need the cast.

That way, change detection stays efficient, and typing is safe (given the requested type change is sound of course).

Unfortunately, I don't see a way to have this generic because of the name attribute, so you'd have to define a new pipe for each type.


This pipe can be used to take the type from various inputs:

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 argument is unused, but is serving the main goal: the type gets inferred from the constructor.

Could be used as:

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>

Requires strict templates and Ivy.