Skip to content

feat(table): typescript/strong typing of cell templates #22290

Open

Description

@andreialecu

Feature Description

Currently, *matCellDef types the resulting variable as any:

Screenshot 2021-03-19 at 11 29 32

This issue is an attempt to bring #16273 back in discussion, which has been closed as Ivy wasn't yet available.

There has been some progress on several fronts:

  • Ivy now has strictTemplates mode.
  • The experimental Ivy powered language server is almost ready to go.

It appears that the approach described in this comment angular/angular#28731 (comment) might allow this to be implemented.

Use Case

As we've been converting more code to use Material Data Tables, the lack of strong typing has been really problematic, and it feels like a downgrade in developer experience compared to regular tables with *ngFor.

Further motivation for this has been described in:

#16273
angular/angular#28731

Activity

  1. added
    featureLabel used to distinguish feature request from other issues
    needs triageThis issue needs to be triaged by the team
    on Mar 19, 2021
  2. andreialecu commented on Mar 19, 2021

    @andreialecu
    Author

    Very simple repro:

    <table mat-table [dataSource]="[{ hello: 'world' }]">
      <tr mat-cell *matCellDef="let element">
        {{
          element.test
        }}
      </tr>
    </table>

    Generates the following typecheck block with Ivy:

    import * as i0 from 'some.component';
    import * as i1 from '@angular/material/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;

    The correct type is available as part of the _t2 variable.

    If _t4 was able to use it instead of any, it seems like this would be achievable.

  3. andreialecu commented on Mar 19, 2021

    @andreialecu
    Author

    Here's a working workaround for this. Most of the credits for it go to @nartc 🏆 , on the Angular Discord.

    import { CdkCellDef } from '@angular/cdk/table';
    import { Directive, Input } from '@angular/core';
    import { MatCellDef } 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 {
      @Input() matCellDefDataSource: T[] | Observable<T[]> | MatTableDataSource<T>;
    
      static ngTemplateContextGuard<T>(
        dir: TypeSafeMatCellDef<T>,
        ctx: any,
      ): ctx is { $implicit: T; index: number } {
        return true;
      }
    }

    You can add this directive to your providers and then use it like:

    <table mat-table [dataSource]="data">
    ...
     <td mat-cell *matCellDef="let element; dataSource: data">
        {{ element.hello }}
     </td>

    And element will no longer be any:
    image

    There's a bit of redundancy, with needing to specify the data source in every *matCellDef that you want type checked, but it's optional. If it isn't defined, it falls back to any just like the original directive.

  4. added
    P3An issue that is relevant to core functions, but does not impede progress. Important, but not urgent
    and removed
    needs triageThis issue needs to be triaged by the team
    on Mar 19, 2021
  5. KingDarBoja commented on Mar 22, 2021

    @KingDarBoja

    Do not forget to follow the steps at Under the hood of the language service to properly enable this workaround.

    • Install the latest extension in the VSCode Marketplace
    • Set strictTemplates: true in tsconfig.json at angularCompilerOptions. This might cause diagnostics to appear if project is not strictTemplates compatible (pretty much strict mode is a must).
    • Open an Angular project in Visual Studio Code.
    • Go to Preferences -> Settings -> Extensions -> Angular Language Service -> Check “Experimental Ivy”.
    • Profit!
  6. aceArt-GmbH commented on Jan 5, 2023

    @aceArt-GmbH
    Contributor

    Is there a solution which would only require 1 change per table instead of 1 change per column?

  7. wongk commented on Apr 17, 2023

    @wongk

    +1

  8. DmitryEfimenko commented on May 21, 2023

    @DmitryEfimenko

    I feel like this issue belongs under @angular/core instead of @angular/components. The fix should allow providing type through a parent directive.

    Maybe it would work if ngTemplateContextGuard accepted 3 arguments?

    static ngTemplateContextGuard<T, P>(
      thisdirective: ThisDirective<T>,
      parentDirective: ParentDirective<P>
      ctx: unknown
    ): ctx is { $implicit: T, parent: P } {
      return true;
    }
  9. Simon-GHI commented on Jan 16, 2024

    @Simon-GHI

    Still waiting for this feature to be integrated in Angular as loosing types in table is still a big disadvantage.

  10. maartentibau commented on Jul 17, 2024

    @maartentibau

    Would love to get this one as well... 🙏

  11. aceArt-GmbH commented on Jul 25, 2024

    @aceArt-GmbH
    Contributor

    For simply using the table reference version you can search-replace with following regex

    \*matCellDef="let (\w*)"
    ↓
    *matCellDef="let $1; table: table"

    And mat-table -> mat-table #table.

    Finds lots of real type errors.
    But also shows many limitations of angular templates. Like optional chaining working different from typescript, numeric index signature ([key: `prop${number}`]) and type narrowing

  12. melroy89 commented on Mar 24, 2025

    @melroy89

    We soon have GTA 6 before matCellDef is fixed.

  13. ahmad-ben commented on Jul 16, 2025

    @ahmad-ben

    Very needed feature...

  14. DorianNaaji commented on Aug 12, 2025

    @DorianNaaji

    Not having types is a real nightmare, and the existing workarounds are not quite satisfying in my humble opinion...

    Definitely waiting for this one 😭

  15. added a commit that references this issue on Jan 6, 2026
  16. marfrede commented on May 12, 2026

    @marfrede

    I rebuilt the quick fix of @andreialecu and @nartc as a CdkCellDef version instead of MatCellDef for users of CdkTable instead of MatTable

    import { CdkCellDef, DataSource } from '@angular/cdk/table';
    import { Directive, Input } from '@angular/core';
    import { Observable } from 'rxjs';
    
    /**
     * IMPORTANT: as soon as https://github.com/angular/components/issues/22290 is solved, this directive can be deleted
     *
     * its only purpose is to make element type safe in `<td cdk-cell *cdkCellDef="let element">…<\td>` when using cdk table.
     */
    @Directive({
      standalone: true,
      // eslint-disable-next-line @angular-eslint/directive-selector
      selector: '[cdkCellDef]', // same selector as CdkCellDef to override by force for type-safety
      providers: [{ provide: CdkCellDef, useExisting: TypeSafeCdkCellDef }], // overwrite CdkCellDef with this typed version of it
    })
    export class TypeSafeCdkCellDef<T> extends CdkCellDef {
      @Input({ required: true }) public cdkCellDefDataSource: T[] | Observable<T[] | DataSource<T>> = [];
    
      public static ngTemplateContextGuard<T>(
        dir: TypeSafeCdkCellDef<T>,
        ctx: unknown
      ): ctx is { $implicit: T; index: number } {
        return true;
      }
    }

    You can add this directive to your providers and then use it like:

    <table mat-table [dataSource]="data">
    ...
     <td mat-cell *matCellDef="let element; dataSource: data">
        {{ element.hello }}
     </td>

    And element will no longer be any:

    Image

    There's a bit of redundancy, with needing to specify the data source in every *cdkCellDef that you want type checked, but it's optional. If it isn't defined, it falls back to any just like the original directive.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3An issue that is relevant to core functions, but does not impede progress. Important, but not urgentarea: cdk/tablefeatureLabel used to distinguish feature request from other issues

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions