27

Having markup like

    <mat-cell *matCellDef="let request">
         <a [href]="request.url" target="_blank">{{request.requestId}}</a>
    </mat-cell>

Can I typehint IDE somehow that request is of type Request? I am using IntelliJ here.

Please be noted, that I am using Angular Material table here, so declaring request in component is not an option here as it is purely template variable. It contains row data provided internally by component itself on every row iteration.

Note that this is perfectly valid markup used in MatDataTable component.

Tharindu Sathischandra
  • 1,654
  • 1
  • 15
  • 37
Antoniossss
  • 31,590
  • 6
  • 57
  • 99
  • Does this answer your question? [Is there a way to add a type assertion / annotation to a template input variable?](https://stackoverflow.com/questions/52087168/is-there-a-way-to-add-a-type-assertion-annotation-to-a-template-input-variable) – luiscla27 Apr 12 '21 at 21:36
  • As far as I can see some answers there says it just should work, others to use casting method stub which is mentioned in answers here as well and which is not what I was hoping for. – Antoniossss Apr 13 '21 at 07:35

6 Answers6

10

as the

tableDataSource: MatTableDataSource<ToDoInterface>;

does not type the model,

this:

<ng-container matColumnDef="toDo">
  <th mat-header-cell *matHeaderCellDef mat-sort-header>ToDo</th>
  <td mat-cell *matCellDef="let model">
    <ng-container *ngIf="assertItemType(model) as toDoModel">
      {{toDoModel.toDo}}
    </ng-container>
  </td>
</ng-container>

where:

assertItemType(item: ToDoInterface): ToDoInterface {
  return item;
}

works.

but not sure if it the best way to do it

Lean Pilar
  • 327
  • 7
  • 13
  • What does that have to do with the issue? – Antoniossss Apr 12 '20 at 10:49
  • Now I'm not sure if I understand your issue. I had the problem that *matCellDef="let model" and later {{model.toDo}} toDo was not recognized by Webstorm, but this way it does. If it's not your issue, I'll remove this answer – Lean Pilar Apr 13 '20 at 10:46
  • ok, sorry, can you explain what is the correct problem because I had a similar problem and I land here before solving my and after solving I shared my solution – Lean Pilar Apr 13 '20 at 10:55
  • The this is that I wanted to typehint IDE just like in php template you can do this via comment (if work with the Webstorm you should be familiar with that). Question was if there is similar way to do this for html templates. And no it seams that there is no typefint heature to this day. – Antoniossss Apr 13 '20 at 10:58
  • 4
    Adding extra method call is just not worth of it – Antoniossss Apr 13 '20 at 11:01
3

This can be solved by wrapping your variable inside another ng-template

The type assertion is noticed by the IDE when *ngFor or *ngIf is in use. This is somehow another workaround, but I liked a lot more than other solutions because it just adds 2 more lines of code in the HTML, of course if you're using your variable only 1 or 2 times this other solution is better. My answer:

Instead of this:

<ng-template *ngTemplateOutlet="foo; context: {$implicit: {fooProp: 'Hello!'}}"></p>
<ng-template #foo let-args>
    This is untyped: {{ args.fooProp }}<br>
</ng-template>

Do this:

<ng-template *ngTemplateOutlet="foo; context: {$implicit: {fooProp: 'Hello!'}}"></p>
<ng-template #foo let-untypedArgs>
    <ng-template [ngIf]="identity(untypedArgs)" let-args="ngIf">
        This is typed: {{ args.fooProp }}<br>
    </ng-template>
</ng-template>
identity(foo: Foo): Foo {
    return foo;
}

With this, now if you add an invalid property to your context, you'll get the following compilation error which is great, here's a stackblitz demo, The downside with this solution is that the inner <ng-template> is rendered later because of the [ngIf].

Property 'newFooProp' does not exist on type 'Foo'.

This is the same answer I gave at this other question.

luiscla27
  • 4,956
  • 37
  • 49
2

Should be careful with functions in templates. Here's a custom pipe I use. It keeps refactoring sane in my IDE (PhpStorm):

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

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

In your template you can use as:

<ng-template let-item="someAnyType">
  <span>{{(item | asType :Customer).name}}</span>
</ng-template>

where you define:

import {Customer} from 'somewhere';

public Customer: typeof Customer = Customer; //Type variable to use in templates
public customer: Customer; //Case sensitivity allows creating similar variables if needed.
1

Although the question is bit older I like to add a method that I used to achieve IDE type-hints.

First we can just add an additional method like,

  asRequest(request: any): Request{
    return workflow as Workflow;
  }

So, now you can wrap the request with that method.

<mat-cell *matCellDef="let request">
     <a [href]="asRequest(request).url" target="_blank">{{asRequest(request).requestId}}</a>
</mat-cell>

So the just request is now asRequest(request). (You can think like its a function for casting. Actually it is!)

I know this way makes some extra function calls, but it will do what you needed.

Tharindu Sathischandra
  • 1,654
  • 1
  • 15
  • 37
0

If you don't have classes and want to use a pipe, you can do something like this:

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

@Pipe({
  name: 'asType',
})
export class AsTypePipe implements PipeTransform {
  transform<T>(value: unknown, type: T): T {
    return value as typeof type;
  }
}

use the data in your component:

 @Input() nodes: IModelNode[] = [];

and use it as

[attr.width]="(node | asType : nodes[0]).dimension?.width"
Welle
  • 1
-3

Now I finally understand the issue (after the OP editing).

You can't specify a type in the template when declaring *matCellDef="let request", but, that local variable should be already typed in your component.

When using the AM Table, we'll have something like this, right?

<mat-table [dataSource]="dataSource">
    <ng-container matColumnDef="name">
      <mat-header-cell *matHeaderCellDef> Name </mat-header-cell>
      <mat-cell *matCellDef="let user"> {{user.name}} </mat-cell>
    </ng-container>
</mat-table>

We can't type the user local variable, but we can type dataSource.

So, Imagine that we have this interface:

export interface User {
    name: string;
    email: string;
}

And that our dataSource is a collection of users. Then we would type it like this:

dataSource: User[] = this.getUsers();

Now we're telling the compiler that each element of dataSource is of type User.

SrAxi
  • 19,787
  • 11
  • 46
  • 65
  • This answer does not cover what I have asked at all. It is a "lecture" about component properties and code assist totally detached from given scenario thus beeing off topic. – Antoniossss Jul 11 '18 at 08:23
  • 1
    @Antoniossss You just edited your post. You initially posted a one-liner... And my answer does cover what you asked initially, because you gave no hints, no concrete information whatsoever. – SrAxi Jul 11 '18 at 08:33
  • Why you can't declare your property in the Component? Angular Material doesn't forbid you to create your properties and use them in the template... – SrAxi Jul 11 '18 at 08:35
  • feel free to try here https://stackblitz.com/angular/pbnxvqjgnnx?file=app%2Ftable-basic-example.html – Antoniossss Jul 11 '18 at 08:47
  • @Antoniossss Gotcha now! :D I updated my answer. *(And my +1 because now the question is complete)* – SrAxi Jul 11 '18 at 08:48
  • I got datasource typed. The point is that markup context is detached so no wonder why IDE does not know what type is it. That is reason why i asked explicitly about TYPEHINTING - there is similar thing in Thymeleaf for example where you declare varible type in html markup as comment. Thanks for the effort. The only viable part of this answer is that it cannot be done :) – Antoniossss Jul 11 '18 at 08:49
  • IDE won't know that we are using content of `let request` will contain items of `dataSource` nor compiler. That what is whole question about - how to typehint IDE. – Antoniossss Jul 11 '18 at 08:54
  • 1
    @Antoniossss Just tested it. Indeed my IDE doesn't recognize that the local variable is of type `User` *(Like in my example)*. It autocompletes but is not detecting the type, is just guessing stuff... – SrAxi Jul 11 '18 at 08:58
  • As you concluded it's irrelevant what type dataSource is, the `*matCellDef` has no way of getting that type which really sucks. – Simon_Weaver Apr 14 '20 at 00:44