Strong-typed

"ng-template let-element"...?

Huh?

Have you ever had this?

Have you ever had this?

<ng-template let-someVar>
	<!-- someVar is what?? πŸ€·β€-->
</ng-template>

Have you ever had this?

<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">

  <!-- Position Column -->
  <ng-container matColumnDef="position">
    <th mat-header-cell *matHeaderCellDef> No. </th>
    <!--                            πŸ‘‡ this is just horrible -->
    <td mat-cell *matCellDef="let element"> {{element.position}} </td>
  </ng-container>

  <!-- for brevity -->

  <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
  <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>

Have you ever had this?

<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">

  <!-- Position Column -->
  <ng-container matColumnDef="position">
    <th mat-header-cell *matHeaderCellDef> No. </th>
    <!--                          πŸ‘‡ and more like -->
    <td mat-cell *matCellDef="let element"> 
      {{element.positionCatchMeIfYouCan}} 
    </td>
  </ng-container>

  <!-- for brevity -->

  <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
  <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>

Have you ever had this?

<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">

  <!-- Position Column -->
  <ng-container matColumnDef="position">
    <th mat-header-cell *matHeaderCellDef> No. </th>
    <!--                          πŸ‘‡ and more like -->
    <td mat-cell *matCellDef="let element"> 
      {{element.positionCatchMeIfYouCan}} 
    </td>
  </ng-container>

  <!-- for brevity -->

  <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
  <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>

Developers want it!

Developers want it!

Developers want it!

Developers want it!

Angular does want to support it

Angular does want to support it

  • Compiler-level technical
  • Complicated
  • Challenging to both internal team and community

Angular does want to support it

<table mat-table [dataSource]="[{ hello: 'world' }]">
  <tr mat-cell *matCellDef="let element">
    {{ element.test }}
  </tr>
</table>
const _ctor1: <T = any>(init: Pick<i1.MatTable<T>, "trackBy" | "dataSource"> & {
    multiTemplateDataRows: typeof i1.MatTable.ngAcceptInputType_multiTemplateDataRows;
    fixedLayout: typeof i1.MatTable.ngAcceptInputType_fixedLayout;
}) => i1.MatTable<T> = (null!);

/*tcb1*/
function _tcb1(ctx: i0.SomeComponent) { if (true) {
    ctx. /*D:ignore*/ /*T:COMPCOMP*/;
    var _t1 = document.createElement("table") /*96,149*/;
    var _t2 /*T:DIR*/ /*96,149*/ = _ctor1({ "dataSource": ([{
                "hello": "world" /*137,144*/
            } /*128,146*/] /*127,147*/) /*113,148*/, "trackBy": (null as any), "multiTemplateDataRows": (null as any), "fixedLayout": (null as any) }) /*D:ignore*/;
    _t2.dataSource /*114,124*/ = ([{
            "hello": "world" /*137,144*/
        } /*128,146*/] /*127,147*/) /*113,148*/;
    var _t3: i1.MatCellDef /*T:DIR*/ /*152,191*/ = (null!);
    var _t4: any = (null!);
    {
        var _t5 /*182,189*/ = _t4.$implicit /*178,189*/;
        var _t6 = document.createElement("tr") /*152,191*/;
        "" + (((_t5 /*205,212*/).test /*213,218*/) /*205,218*/);
    }
} }

export const IS_A_MODULE = true;

Angular does want to support it

<table mat-table [dataSource]="[{ hello: 'world' }]">
  <tr mat-cell *matCellDef="let element">
    {{ element.test }}
  </tr>
</table>
const _ctor1: <T = any>(init: Pick<i1.MatTable<T>, "trackBy" | "dataSource"> & {
    multiTemplateDataRows: typeof i1.MatTable.ngAcceptInputType_multiTemplateDataRows;
    fixedLayout: typeof i1.MatTable.ngAcceptInputType_fixedLayout;
}) => i1.MatTable<T> = (null!);

/*tcb1*/
function _tcb1(ctx: i0.SomeComponent) { if (true) {
    ctx. /*D:ignore*/ /*T:COMPCOMP*/;
    var _t1 = document.createElement("table") /*96,149*/;
    var _t2 /*T:DIR*/ /*96,149*/ = _ctor1({ "dataSource": ([{
                "hello": "world" /*137,144*/
            } /*128,146*/] /*127,147*/) /*113,148*/, "trackBy": (null as any), "multiTemplateDataRows": (null as any), "fixedLayout": (null as any) }) /*D:ignore*/;
    _t2.dataSource /*114,124*/ = ([{
            "hello": "world" /*137,144*/
        } /*128,146*/] /*127,147*/) /*113,148*/;
    var _t3: i1.MatCellDef /*T:DIR*/ /*152,191*/ = (null!);
    var _t4: any = (null!);
    {
        var _t5 /*182,189*/ = _t4.$implicit /*178,189*/;
        var _t6 = document.createElement("tr") /*152,191*/;
        "" + (((_t5 /*205,212*/).test /*213,218*/) /*205,218*/);
    }
} }

export const IS_A_MODULE = true;

Angular does want to support it

<table mat-table [dataSource]="[{ hello: 'world' }]">
  <tr mat-cell *matCellDef="let element">
    {{ element.test }}
  </tr>
</table>
const _ctor1: <T = any>(init: Pick<i1.MatTable<T>, "trackBy" | "dataSource"> & {
    multiTemplateDataRows: typeof i1.MatTable.ngAcceptInputType_multiTemplateDataRows;
    fixedLayout: typeof i1.MatTable.ngAcceptInputType_fixedLayout;
}) => i1.MatTable<T> = (null!);

/*tcb1*/
function _tcb1(ctx: i0.SomeComponent) { if (true) {
    ctx. /*D:ignore*/ /*T:COMPCOMP*/;
    var _t1 = document.createElement("table") /*96,149*/;
    var _t2 /*T:DIR*/ /*96,149*/ = _ctor1({ "dataSource": ([{
                "hello": "world" /*137,144*/
            } /*128,146*/] /*127,147*/) /*113,148*/, "trackBy": (null as any), "multiTemplateDataRows": (null as any), "fixedLayout": (null as any) }) /*D:ignore*/;
    _t2.dataSource /*114,124*/ = ([{
            "hello": "world" /*137,144*/
        } /*128,146*/] /*127,147*/) /*113,148*/;
    var _t3: i1.MatCellDef /*T:DIR*/ /*152,191*/ = (null!);
    var _t4: any = (null!);
    {
        var _t5 /*182,189*/ = _t4.$implicit /*178,189*/;
        var _t6 = document.createElement("tr") /*152,191*/;
        "" + (((_t5 /*205,212*/).test /*213,218*/) /*205,218*/);
    }
} }

export const IS_A_MODULE = true;

Angular does want to support it

<table mat-table [dataSource]="[{ hello: 'world' }]">
  <tr mat-cell *matCellDef="let element">
    {{ element.test }}
  </tr>
</table>
const _ctor1: <T = any>(init: Pick<i1.MatTable<T>, "trackBy" | "dataSource"> & {
    multiTemplateDataRows: typeof i1.MatTable.ngAcceptInputType_multiTemplateDataRows;
    fixedLayout: typeof i1.MatTable.ngAcceptInputType_fixedLayout;
}) => i1.MatTable<T> = (null!);

/*tcb1*/
function _tcb1(ctx: i0.SomeComponent) { if (true) {
    ctx. /*D:ignore*/ /*T:COMPCOMP*/;
    var _t1 = document.createElement("table") /*96,149*/;
    var _t2 /*T:DIR*/ /*96,149*/ = _ctor1({ "dataSource": ([{
                "hello": "world" /*137,144*/
            } /*128,146*/] /*127,147*/) /*113,148*/, "trackBy": (null as any), "multiTemplateDataRows": (null as any), "fixedLayout": (null as any) }) /*D:ignore*/;
    _t2.dataSource /*114,124*/ = ([{
            "hello": "world" /*137,144*/
        } /*128,146*/] /*127,147*/) /*113,148*/;
    var _t3: i1.MatCellDef /*T:DIR*/ /*152,191*/ = (null!);
    var _t4: any = (null!);
    {
        var _t5 /*182,189*/ = _t4.$implicit /*178,189*/;
        var _t6 = document.createElement("tr") /*152,191*/;
        "" + (((_t5 /*205,212*/).test /*213,218*/) /*205,218*/);
    }
} }

export const IS_A_MODULE = true;

Angular does want to support it

<table mat-table [dataSource]="[{ hello: 'world' }]">
  <tr mat-cell *matCellDef="let element">
    {{ element.test }}
  </tr>
</table>
const _ctor1: <T = any>(init: Pick<i1.MatTable<T>, "trackBy" | "dataSource"> & {
    multiTemplateDataRows: typeof i1.MatTable.ngAcceptInputType_multiTemplateDataRows;
    fixedLayout: typeof i1.MatTable.ngAcceptInputType_fixedLayout;
}) => i1.MatTable<T> = (null!);

/*tcb1*/
function _tcb1(ctx: i0.SomeComponent) { if (true) {
    ctx. /*D:ignore*/ /*T:COMPCOMP*/;
    var _t1 = document.createElement("table") /*96,149*/;
    var _t2 /*T:DIR*/ /*96,149*/ = _ctor1({ "dataSource": ([{
                "hello": "world" /*137,144*/
            } /*128,146*/] /*127,147*/) /*113,148*/, "trackBy": (null as any), "multiTemplateDataRows": (null as any), "fixedLayout": (null as any) }) /*D:ignore*/;
    _t2.dataSource /*114,124*/ = ([{
            "hello": "world" /*137,144*/
        } /*128,146*/] /*127,147*/) /*113,148*/;
    var _t3: i1.MatCellDef /*T:DIR*/ /*152,191*/ = (null!);
    var _t4: any = (null!);
    {
        var _t5 /*182,189*/ = _t4.$implicit /*178,189*/;
        var _t6 = document.createElement("tr") /*152,191*/;
        "" + (((_t5 /*205,212*/).test /*213,218*/) /*205,218*/);
    }
} }

export const IS_A_MODULE = true;

What can we do?

What can we do?

Directive

What do we know?

<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">

  <!-- Position Column -->
  <ng-container matColumnDef="position">
    <th mat-header-cell *matHeaderCellDef> No. </th>
    <!--                            πŸ‘‡ this is just horrible -->
    <td mat-cell *matCellDef="let element"> {{element.position}} </td>
  </ng-container>

  <!-- for brevity -->

  <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
  <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>

Let's do what devs do: hack

import { CdkCellDef } from '@angular/cdk/table';
import { Directive, Input } from '@angular/core';
import { MatCellDef, MatTableDataSource } from "@angular/material/table";
import { Observable } from 'rxjs';

@Directive({
  selector: '[matCellDef]', // same selector as MatCellDef
  providers: [{ provide: CdkCellDef, useExisting: TypeSafeMatCellDef }],
})
export class TypeSafeMatCellDef<T> extends MatCellDef {
  // leveraging syntactic-sugar syntax when we use *matCellDef  
  @Input() matCellDefDataSource: T[] | Observable<T[]> | MatTableDataSource<T>;

  // ngTemplateContextGuard flag to help with the Language Service
  static ngTemplateContextGuard<T>(
    dir: TypeSafeMatCellDef<T>,
    ctx: unknown,
  ): ctx is { $implicit: T; index: number } {
    return true;
  }
}

Let's do what devs do: hack

import { CdkCellDef } from '@angular/cdk/table';
import { Directive, Input } from '@angular/core';
import { MatCellDef, MatTableDataSource } from "@angular/material/table";
import { Observable } from 'rxjs';

@Directive({
  selector: '[matCellDef]', // same selector as MatCellDef
  providers: [{ provide: CdkCellDef, useExisting: TypeSafeMatCellDef }],
})
export class TypeSafeMatCellDef<T> extends MatCellDef {
  // leveraging syntactic-sugar syntax when we use *matCellDef  
  @Input() matCellDefDataSource: T[] | Observable<T[]> | MatTableDataSource<T>;

  // ngTemplateContextGuard flag to help with the Language Service
  static ngTemplateContextGuard<T>(
    dir: TypeSafeMatCellDef<T>,
    ctx: unknown,
  ): ctx is { $implicit: T; index: number } {
    return true;
  }
}
<!-- ngForOfTrackBy -->
<div *ngFor="let item of items;trackBy: trackByFn"></div>

<!-- ngTemplateOutletContext -->
<ng-container *ngTemplateOutlet="template;context: {}"></ng-container>

Let's do what devs do: hack

import { CdkCellDef } from '@angular/cdk/table';
import { Directive, Input } from '@angular/core';
import { MatCellDef, MatTableDataSource } from "@angular/material/table";
import { Observable } from 'rxjs';

@Directive({
  selector: '[matCellDef]', // same selector as MatCellDef
  providers: [{ provide: CdkCellDef, useExisting: TypeSafeMatCellDef }],
})
export class TypeSafeMatCellDef<T> extends MatCellDef {
  // leveraging syntactic-sugar syntax when we use *matCellDef  
  @Input() matCellDefDataSource: T[] | Observable<T[]> | MatTableDataSource<T>;

  // ngTemplateContextGuard flag to help with the Language Service
  static ngTemplateContextGuard<T>(
    dir: TypeSafeMatCellDef<T>,
    ctx: unknown,
  ): ctx is { $implicit: T; index: number } {
    return true;
  }
}

Let's do what devs do: hack

@NgModule({
  declarations: [TypeSafeMatCellDef]
})
export class Core/Shell/AppModule {}

Let's do what devs do: hack

<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">

  <!-- Position Column -->
  <ng-container matColumnDef="position">
    <th mat-header-cell *matHeaderCellDef> No. </th>
    <!--                          πŸ‘‡this is horrible -->
    <td mat-cell *matCellDef="let element"> 
      {{element.position}} 
    </td>
  </ng-container>

  <!-- for brevity -->

  <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
  <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>

Let's do what devs do: hack

<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">

  <!-- Position Column -->
  <ng-container matColumnDef="position">
    <th mat-header-cell *matHeaderCellDef> No. </th>
    <!--                                 πŸ‘‡magic πŸ¦„πŸŒˆ -->
    <td mat-cell *matCellDef="let element;dataSource: dataSource"> 
      {{element.position}} 
    </td>
  </ng-container>

  <!-- for brevity -->

  <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
  <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>

Let's do what devs do: hack

Let's do what devs do: hack

Reference

Q/A

strong-type-ng-template

By Chau Tran

strong-type-ng-template

  • 681