angular icon indicating copy to clipboard operation
angular copied to clipboard

Pipe `keyvalue` does not allow objects with optional keys.

Open infacto opened this issue 2 years ago β€’ 1 comments

Which @angular/* package(s) are the source of the bug?

core

Is this a regression?

No

Description

An error occurs in HTML when using an interface (object) with option keys.

app.component.ts

import { Component } from '@angular/core';

export interface MyInterface {
  one?: string;
  two?: string;
  three?: string;
}

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
})
export class AppComponent {
  myData: MyInterface = {
    one: 'One',
    two: 'Two',
  };
}

app.component.html

<ng-container *ngFor="let entry of myData | keyvalue">
  <div>{{ entry.key }}: {{ entry.value }}</div>
</ng-container>

VSCode with Angular Language Service image

No error when using serve but on build (prod). The VSCode extension Angular Language Service also shows this error in HTML. Never seen before. So I'm not 100% if this is a regression. But maybe I never worked with interface with an optional key here. Anyway, the code works during runtime. It seems to be a type interference. We need to allow optional keys (undefined).

Please provide a link to a minimal reproduction of the bug

https://github.com/infacto/issue-angular-keyvalue-optional/tree/main

Please provide the exception or error you saw

No overload matches this call.
  The last overload gave the following error.
    Argument of type 'MyInterface' is not assignable to parameter of type 'Record<keyof MyInterface, string> | ReadonlyMap<keyof MyInterface, string>'.ngtsc(2769)

Please provide the environment you discovered this bug in (run ng version)

Angular CLI: 13.3.8
Node: 14.19.0
Package Manager: npm 6.14.16
OS: win32 x64

Angular: 13.3.11
... animations, common, compiler, compiler-cli, core, forms
... language-service, platform-browser, platform-browser-dynamic
... router

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1202.15
@angular-devkit/build-angular   13.3.8
@angular-devkit/core            12.2.15
@angular-devkit/schematics      12.2.15
@angular/cli                    13.3.8
@schematics/angular             12.2.15
rxjs                            6.6.7
typescript                      4.6.4

Anything else?

I tested this in the latest Angular 12, 13 and 14. I see no error on StackBlitz (no Language Service ext.) and the code works well during runtime.

The error disappears when allow additional keys like this:


export interface MyInterface {
  one?: string;
  two?: string;
  three?: string;
  [key: string]: string;
}

Or casting to any. But these is just ugly and against clean or professional code.

infacto avatar Jul 18 '22 11:07 infacto

Hello !

Having the same issue on Angular 14.2.

Here is a minimalist reproduction of the issue ☺️

Reproduction of the bug

Template (not changed during reproduction steps)

        <ng-container *ngFor="let metadata of keypipeIssue.metadata | keyvalue">
          {{metadata.key}}: {{metadata.value}}
        </ng-container>

Component (not changed during reproduction steps)

  keypipeIssue: KeyPipeIssueTestObject = {
    metadata: {
      thing: "test"
    }
  }

Case 1: interface, no optionals

interface KeyPipeIssueTestObjectMetadata {
  thing: string
}

interface KeyPipeIssueTestObject {
  metadata: KeyPipeIssueTestObjectMetadata
}

--> βœ”οΈ working

Case 2: interface, optionals

interface KeyPipeIssueTestObjectMetadata {
  thing?: string
}

interface KeyPipeIssueTestObject {
  metadata: KeyPipeIssueTestObjectMetadata
}

--> ❌ template error

No overload matches this call.
  The last overload gave the following error.
    Argument of type 'KeyPipeIssueTestObjectMetadata' is not assignable to parameter of type 'Record<"thing", string | undefined> | ReadonlyMap<"thing", string | undefined> | null | undefined'.ngtsc(2769)

Case 3: no interface, optionals

interface KeyPipeIssueTestObject {
  metadata: {
    thing?: string
  }
}

--> βœ”οΈ working ... ???

Case 4: no interface, no optionals

interface KeyPipeIssueTestObject {
  metadata: {
    thing: string
  }
}

--> βœ”οΈ working ... of course πŸ˜„

Case 5: interface, optionals with additional keys

interface KeyPipeIssueTestObjectMetadata {
  thing?: string
  [key: string]: string | undefined // or any
}

interface KeyPipeIssueTestObject {
  metadata: KeyPipeIssueTestObjectMetadata
}

--> βœ”οΈ working


Hope that helps !

Quentame avatar Nov 22 '22 16:11 Quentame

*** AppComponent *** export class AppComponent { name = 'Angular';

myData: MyInterface = { one: 'myOne', two: 'mytwo', }; }

export interface MyInterface { one?: string; two?: string; three?: string; [key: string]: string; }

app.component.html

  • {{ item.key }} ---> {{ item.value }}

***Result *** one ---> myOne two ---> mytwo

Pipe with Optionala parameters work fine.

VrushaliRThorat avatar Dec 14 '22 09:12 VrushaliRThorat

@pkozlowski-opensource Hi there, I see that there is a PR opened for this issue and its approved, but the movement inside it went stale. Is there any update on this issue?

PaskalevStoyan avatar May 11 '23 14:05 PaskalevStoyan

It still needs approvals from the Angular team !

JeanMeche avatar May 11 '23 14:05 JeanMeche