34

Background

I have a template that looks like this (I'm using some component that uses this as the basis for a repeated item, it's the <p-pickList>, but the question is not specific about that component, just as an example)

For background, let's say I have a type Foo and my component has a foos: Foo[], I'm feeding it to the <p-pickList> component under the [source] attribute and its doing the internal *ngFor for it, all I have to do is provide a template

 <ng-template let-foo pTemplate="item">
   ...
   {{ foo.anythingGoesHereWithNoWarningAndNoAutocomplete }}

However, the type information on foo seems to be lost.

I'm a big fan of type safety and I like that Intellij (or any other editor) can show me a warning if inside the template I do something like specifying an invalid attribute of foo

If I had a regular *ngFor, it would infer the type of foo

<div *ngFor="let foo of foos">
  {{ foo.autoCompleteWorksInMostIDEsAsWellAsWarningIfInvalidProp }}

Questions:

  1. Is there any syntax that will allow me to hint the type of let-foo? (and hopefully most IDE's will recognize).

  2. If I don't want to rely on IDE's, is there a way to have the ng compiler type check foo (declared by let-foo)?

tl;dr is there a syntax that let me type annotate the template input variable? e.g. something like this made up syntax?

let-foo="$implicit as Foo" or let-foo-type="Foo"?

Workaround

One silly idea is to have an identity function in my component, e.g.

identity(foo: Foo): Foo {
  return foo;
}

But doing

{{ identity(foo).fooProp }}

Is not a big improvement over

{{ (foo as Foo).fooProp }}`
luiscla27
  • 4,956
  • 37
  • 49
Eran Medan
  • 44,555
  • 61
  • 184
  • 276
  • 1
    Not sure if `fulltemplatetypecheck` option would work? https://angular.io/guide/aot-compiler#fulltemplatetypecheck – David Sep 06 '18 at 14:09
  • maybe a pipe that receive the type parameter? something like {{ youwhareverData | typeChecker:FooClass }} – hamilton.lima Sep 07 '18 at 15:06
  • I think your prod build will fail if that variable doesn't exist. To have intellisense, I think you need an extension for your editor. – Boland Sep 10 '18 at 03:43
  • 1
    This is more like a feature request that you need to submit to Angular team – Milad Sep 12 '18 at 04:00
  • 1
    Does this answer your question? [ng-template - typed variable](https://stackoverflow.com/questions/55458421/ng-template-typed-variable) – Dane Brouwer Nov 18 '20 at 09:38
  • 1
    Prod build will not recognise if an argument doesn't exists even with fullTemplateTypeCheck and strictTemplates – Davide Apr 05 '21 at 16:02
  • Shouldn't this be addressed in one of upcoming Angular releases? – Marek Apr 12 '23 at 08:56

5 Answers5

4

Let's see what angular has similar to that and how it work!

<p *ngFor="let number of [{v: 101},{v: 102}, {v: 103}]">{{number.v}}</p>

We can rewrite it without * magic

<ng-template ngFor let-number [ngForOf]="[{v: 101},{v: 102}, {v: 103}]">
  <p>{{number.v}}</p>
</ng-template>

Without watchers (ngDoCheck) it can be the same as (but ngTemplateOutlet have no typecheck):

<ng-template let-number #templateRef>
  <p>{{number.v}}</p>
</ng-template>
<ng-container *ngTemplateOutlet="templateRef; context: {$implicit: {v: 101}}"></ng-container>
<ng-container *ngTemplateOutlet="templateRef; context: {$implicit: {v: 102}}"></ng-container>
<ng-container *ngTemplateOutlet="templateRef; context: {$implicit: {v: 103}}"></ng-container>

Or we can create it by ourselves

// template
<button (click)=create(templateRef)>create</button>
// TS
constructor(private _viewContainerRef: ViewContainerRef) { ... }

create(templateRef: TemplateRef<{$implicit: {v: number;}}>) {
    this._viewContainerRef.createEmbeddedView(templateRef, {$implicit: {v: 101}});
    this._viewContainerRef.createEmbeddedView(templateRef, {$implicit: {v: 102}});
    this._viewContainerRef.createEmbeddedView(templateRef, {$implicit: {v: 103}});
}

TL;DR

Template typecheck magic happens inside viewContainerRef.createEmbeddedView. (for example ngFor); But it assume what templateRef accepts.

Angular can compile in AOT:

<p *ngFor="let num of [{v:1}, {v:2}]">
  {{num.does.not.exist.completly}}
</p>

So as i understood we should assume what types templates have but do check when template is instantiated (by createEmbeddedView);

luiscla27
  • 4,956
  • 37
  • 49
Buggy
  • 3,539
  • 1
  • 21
  • 38
2

The problem is that there is no any type information. <ng-template let-foo pTemplate="item"> - is just template declaration, it is not yet bound to any type/context, just some dynamic type that has or may have anythingGoesHereWithNoWarningAndNoAutocomplete field. Without proper generics support it is not possible to have this kind of typesafety.

kemsky
  • 14,727
  • 3
  • 32
  • 51
2

Found this issue and proposed solution to allow template input variables to publish type information so that IDEs can provide better completion on github here. This feature is currently under development and hopefully it'll be out in the future release of Angular. You should use Angular Language Service extension in VS Code or Sublime Text for now. You can rest assured about the extension's support because it's being worked on by the Angular team. For more info, checkout this readme and docs.

As you mentioned it can be achieved by using identity function but it will cause heavy performance issue and it'll be a mess maintaining this functionality for different types.

Abdul Rafay
  • 3,241
  • 4
  • 25
  • 50
2

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

This is somehow another workaround, but I liked a lot more than the other solutions here 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:

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

Do this:

<p *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;
}

As stated already by everyone, the type assertion is noticed by the IDE when *ngFor is in use. It also works when using *ngIf. Of course there's a downside with this solution as the inner <ng-template> is rendered later because of the [ngIf].

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:

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

I came up with this solution by just reading the documentation here:

enter image description here

I tested this solution and works on Angular 11 using vscode with angularCompilerOptions properties enableIvy and fullTemplateTypeCheck setted on true.

Also, Angular Language Service should be installed, this solution is needed even with ALS installed because the template can be called from anywhere, and so, ALS doesn't have a way to know the context params being passed. This can be easily tested in the stackblitz demo I've added, just rename fooProp to fooProp1, you'll see that the untyped example shows an empty value, and the typed one throws an error.

Mart
  • 5,608
  • 3
  • 32
  • 45
luiscla27
  • 4,956
  • 37
  • 49
  • I believe you meant ``, but even having the right `angularCompilerOptions` the variable is still considered as `any` in my tests. – Mart Apr 20 '21 at 08:21
  • This solution is not hack, It's just taking advantage of the expected behaviour of `[ngIf]`, So please let us know if you found the reason of why it doesn't work in your tests! – luiscla27 Apr 20 '21 at 16:38
  • @Mart, I've fixed the code to `identity(untypedArgs)`, thank you. Also, i've added a reference to Angular Language Service, do you have that plugin installed? https://marketplace.visualstudio.com/items?itemName=Angular.ng-template – luiscla27 Apr 20 '21 at 17:03
  • 1
    Yes, I have the Angular Language Service plugin and this trick worked on a new simple project. I have to figure out why my large project doesn't behave the same way. – Mart Apr 21 '21 at 07:23
-1

VSCode should have that feature out of the box, just tested in stackblitz: https://stackblitz.com/edit/angular-type-assertion?file=src/app/app.component.html

enter image description here

hamilton.lima
  • 1,902
  • 18
  • 29
  • Are you sure it isn't using an extension? My VS Code on Windows 10 does not have this. I know that this extension can do HTML intellisense: https://marketplace.visualstudio.com/items?itemName=Angular.ng-template – Boland Sep 10 '18 at 03:44
  • Hey Boland! I this example I used the VSCode online by stackblitz, I couldn't find any documentation about any plugin they use in VSCode – hamilton.lima Sep 10 '18 at 11:51
  • 3
    Stackblitz uses [Angular Language Service](https://angular.io/guide/language-service) – CornelC Sep 12 '18 at 12:30
  • 2
    @hamilton.lima this question is about type assertion / annotation to a template input variable. Reference to object type in template input variable gets lost even on Stackblitz. – Abdul Rafay Sep 13 '18 at 05:35
  • @abdulrafay as you can see from the screenshot stackblitz was able to validate if the attribute name was valid for that object, so the information about type is not lost – hamilton.lima Sep 13 '18 at 13:13
  • 3
    you are not checking template input variable in your code. Updated your Stackblitz here: https://stackblitz.com/edit/angular-type-assertion-wndgva?file=src/app/app.component.html check that type info is being lost in `ng-template` `let-foo` – Abdul Rafay Sep 13 '18 at 13:53
  • Good point! I rewrote your block changing to *ngFor and that keeps the type, take a look https://stackblitz.com/edit/angular-type-assertion?file=src%2Fapp%2Fapp.component.html – hamilton.lima Sep 13 '18 at 14:04
  • You are showing what he already told us in question: *If I had a regular *ngFor, it would infer the type of foo*. Your code block is still not using [template input variables][1]. Question clearly asks for this: *Is there any syntax that will allow me to hint the type of let-foo?* [1]: https://angular.io/guide/template-syntax#template-input-variable – Abdul Rafay Sep 14 '18 at 07:26
  • How come this is accepted answer.... – Antoniossss Mar 15 '22 at 07:30