question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Language service does not infer generic type from component @Input

See original GitHub issue

🐞 bug report

Affected Package

The issue is caused by package @angular/language-service

Is this a regression?

No. To my knowledge it has never worked.

Description

I’ve found several issues when using generic components.

Given this generic component

@Component({
  selector: "test",
  template: "",
})
export class TestComponent<T> {
  @Input() myData: T[] = [];
  @Output() out = new EventEmitter<T[]>();

  public data: T[] = [];
}

Then when listOfPeople: Person[]

<test #peopleTest [myData]="listOfPeople" (out)="doStuff($event)"></test> <!-- $event: T[] is not a valid input to doStuff(string) -->

<ng-container *ngIf="peopleTest.data as data2"> <!-- no hover over `as data2` -->
  {{ data2 }} <!-- data2 is any, should be Person[] -->
</ng-container> 
<ng-container *ngIf="peopleTest.data == listOfStrings"></ng-container> <!-- these should not be comparable -->
<ng-container *ngIf="peopleTest.data[0] == singleString"></ng-container> <!-- if `peopleTest.data == listOfStrings` is comparable, why isn't this? -->
<ng-container *ngIf="peopleTest.data[0] == singlePerson"></ng-container> <!-- these should be comparable -->

While most of these we can live without. The 2nd, that being data2 is any, is a real pain point.

Here’s a real world example where losing the type becomes painful.

<nz-table #basicTable [nzData]="listOfPeople">
  <thead>
    <tr>
      <th>Name</th>
      <th>Age</th>
      <th>Address</th>
      <th>Action</th>
    </tr>
  </thead>
  <tbody>
    <tr *ngFor="let data of basicTable.data"> <!-- no hover over `let data` -->
      <!-- 
          data is any, should be Person
          This opens up for silent typo errors etc
          Also refactor tooling doesn't work on these
      -->
      <td>{{ data.name }}</td>
      <td>{{ data.age }}</td>
      <td>{{ data.address }}</td>
      <td>
        <a>Action 一 {{ data.name }}</a>
        <nz-divider nzType="vertical"></nz-divider>
        <a>Delete</a>
      </td>
    </tr>
  </tbody>
</nz-table>

image image

🔬 Minimal Reproduction

clone https://github.com/snebjorn/angular-n3eeck open app.component.html and observe the errors and missing type info

🔥 Exception or Error

Referring to the above examples:

  1. data and data2 should infer the correct generic type (Person[])
  2. (out)="doStuff($event)" should give an error as doStuff only accepts a string parameter
  3. *ngIf="peopleTest.data == listOfStrings" should not be comparable
  4. *ngIf="peopleTest.data[0] == singlePerson" should be comparable

🌍 Your Environment

Angular Version:


Angular CLI: 10.2.0
Node: 12.16.1
OS: win32 x64

Angular: 10.2.3
... animations, common, compiler, compiler-cli, core, forms
... language-service, platform-browser, platform-browser-dynamic
... router
Ivy Workspace: Yes

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1002.0
@angular-devkit/build-angular   0.1002.0
@angular-devkit/core            10.2.0
@angular-devkit/schematics      10.2.0
@angular/cdk                    10.2.7
@angular/cli                    10.2.0
@schematics/angular             10.2.0
@schematics/update              0.1002.0
rxjs                            6.6.3
typescript                      4.0.5

Anything else relevant? This is an continuation of #33055 ish

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:7
  • Comments:6 (3 by maintainers)

github_iconTop GitHub Comments

3reactions
Airbladercommented, Nov 10, 2020

FWIW, we’re also using ng-zorro and work around this issue, as well as all other typing shortcomings in templates (I’ve posted this in those other issues as well), using something like

public readonly narrowPersons = (persons: unknown): Person[] => persons as Person[];

with

*ngFor="let person of narrowPersons(basicTable.data)"

It’s not ideal as technically it’s just a type assertion, but it avoids the any and you’re more likely to change the Person type structurally than replace the type of the data altogether anyway.

Overall it’s definitely a little disappointing how quickly the typing abilities of Angular fall apart given that it was built on TS with the purpose of being type-safe. There is supposed to be ongoing work for the language service with Ivy though, but I have no idea what that entails in detail.

Generic type arguments on components are, in my experience, definitely not exotic but common and I wish this would work better out of the box as well.

1reaction
snebjorncommented, Nov 12, 2020

Seems something is coming down the pipe 🎉

https://blog.angular.io/version-11-of-angular-now-available-74721b7952f7

Updated Language Service Preview The Angular Language Service provides helpful tools to make development with Angular productive and fun. The current version of the language service is based on View Engine and today we’re giving a sneak peek of the Ivy-based language service. The updated language service provides a more powerful and accurate experience for developers.

Now, the language service will be able to correctly infer generic types in templates the same way the TypeScript compiler does. For example, in the screenshot below we’re able to infer that the iterable is of type string. image Screenshot of intellisense style insights in Angular templates. Angular Language Service inferring iterable types in templates This powerful new update is still in development but we wanted to share an update as we keep preparing it for a full release in an upcoming version.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Returned type of a function is not inferred when it's ...
Returned type of a function is not inferred when it's declared as generic from the function's argument · Ask Question. Asked 10 months...
Read more >
How To Use Generics in TypeScript
TypeScript here is inferring the generic type from the calling code itself. This way the calling code does not need to pass any...
Read more >
TypeScript for React Developers
TypeScript, Java, and a bunch of other languages have static typing that will define a type associated with a variable. The type will...
Read more >
Typed Forms
If you want to have multiple different element types inside the array, you must use UntypedFormArray , because TypeScript cannot infer which element...
Read more >
Generics - F# | Microsoft Learn
When you use generic functions or methods, you might not have to specify the type arguments. The compiler uses type inference to infer...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found