NgForOf指令在Angular中为什么不能与联合类型一起使用?

huangapple go评论78阅读模式
英文:

Why doesn't NgForOf directive in Angular work with union types?

问题

在我的组件中,我有一个属性数组,定义如下:

array: number[] | string[] = ['1', '2'];


在模板中,我正在使用ngFor循环遍历这个数组的元素:

{{element}}

```

但编译器报错如下:

error TS2322:
Type 'number[] | string[]' is not assignable to
type '(number[] & NgIterable<number>) | null | undefined'.

我不明白为什么 Angular 推断属性 array 的类型应该是 number[] & NgIterable<number>

我知道我可以在模板中使用 $any() 来消除错误,或者在 tsconfig.json 中设置 angularCompilerOptions.strictTemplatesfalse,但如果没有必要的话,我想避免这样做。

我也不喜欢将 array 属性的类型更改为 (number | string)[],因为那样是不正确的,我希望 array 只包含数字或只包含字符串。


<details>
<summary>英文:</summary>

In my component I have a property array defined like this:

array: number[] | string[] = ['1', '2'];


And in template I&#39;m iterating over the elements of this array using ngFor:

<div *ngFor="let element of array">
{{element}}
</div>


But compiler throws this error:

error TS2322:
Type 'number[] | string[]' is not assignable to
type '(number[] & NgIterable<number>) | null | undefined'.


I don&#39;t understand how and why Angular infers that the type of property `array` should be `number[] &amp; NgIterable&lt;number&gt;` in this case.

I know I can get rid of the error by using `$any()` inside the template, or setting `angularCompilerOptions.strictTemplates` inside `tsconfig.json` to `false`, but I would like to avoid doing that if not necessary.

I also don&#39;t like the idea of changing the type of the `array` property to `(number | string)[]` because that would be wrong, I want `array` to contain only numbers, or only strings.

</details>


# 答案1
**得分**: 3

这确实是Angular中的一个错误。`NgFor`的代码是正确的,您的代码也是正确的。请看下面的示例,它可以正常编译:

```typescript
type NgIterable<T> = T[] | Iterable<T>;

const f = <T, U extends NgIterable<T> = NgIterable<T>>(param: (U & NgIterable<T>) | undefined | null) => false;

const a: string[] | number[] = ['1'];

window.console.info(f(a));

Angular会将模板转换为TypeScript代码,并为您的模板生成“粘合”代码,这就是错误的发生地点。Angular必须自行“推断”模板变量的类型(例如通过分析AST),然后将其输入生成的代码中。

在您的示例中,Angular错误地将类型推断为number[]

然后,在生成的代码中的某个地方,它可能有一个临时变量、常量或属性,其所需的(且不正确的)类型是(number[] & NgIterable<number>) | null | undefined,例如:

//...
// templateVar类型是"string[] | number[]",它来自您的代码
// 无法编译
const temp: (number[] & NgIterable<number>) | null | undefined = templateVar;
ngForInstance.ngForOf = temp;
//...
英文:

It is indeed a bug in Angular. NgFor code is correct, your code is also correct. See example below, it compiles just fine:

type NgIterable&lt;T&gt; = T[] | Iterable&lt;T&gt;;

const f = &lt;T, U extends NgIterable&lt;T&gt; = NgIterable&lt;T&gt;&gt;(param: (U &amp; NgIterable&lt;T&gt;) | undefined | null) =&gt; false;

const a: string[] | number[] = [&#39;1&#39;];

window.console.info(f(a)); 

Angular transforms templates into TS, it generates "glue" code for your templates and this is where the bug is. Angular has to "infer" template variable types itself (e.g. by analysing AST) and then feed it into generated code.

In your example Angular incorrectly infers type as number[].

Then somewhere in generated code they have temp variable or constant or property that has desired (and incorrect) type which is (number[] &amp; NgIterable&lt;number&gt;) | null | undefined, e.g.:

//...
// templateVar type is &quot;string[] | number[]&quot;, it comes from your code
// does not compile
const temp: (number[] &amp; NgIterable&lt;number&gt;) | null | undefined = templateVar; 
ngForInstance.ngForOf = temp;
//...

答案2

得分: 1

你可以创建一个获取器,将你的数组“转换”为以下方式:

get castArray() {
  return this.array as NgIterable<number|string>;
}

然后在 *ngFor 中使用 castArray

<div *ngFor="let element of castArray">
   {{element}}
</div>

但实际上,我觉得这是一种“不太优雅”的方法。

英文:

You can create a getter that "cast" your array

  get castArray()
  {
    return this.array as NgIterable&lt;number|string&gt;
  }

And use castArray in the *ngFor

&lt;div *ngFor=&quot;let element of castArray&quot;&gt;
   {{element}}
&lt;/div&gt;

But really I feel that it's an "ugly" approach.

答案3

得分: 1

你可以使用 array: Array&lt;number|string&gt;; 来创建一个数组的联合类型。就我所知,联合类型在 TypeScript 中从来都不太好用,但对于简单类型的 a|b,应该可以正常工作,我认为。

另外,不要将你的数组命名为 array。这是一个保留关键字,可能会引发问题。

英文:

For an array, you can make a union type using array: Array&lt;number|string&gt;;. Union types never have worked well in TS (as far as I know), but with simple types a|b it should work ok, I think.

As a sidenote, don't call your array array. This is a reserved keyword and is bound to cause issues.

答案4

得分: 0

这是一个有趣的 issue,您可以使用 typescript 4.9 的类型特性 satisfies 轻松解决它:

array = ['1', '2'] satisfies (number[] | string[]);

现在不再有错误。

英文:

This is interesting issue and you can easily solve it with typescript 4.9 typing feature satisfies:

array = [&#39;1&#39;, &#39;2&#39;] satisfies (number[] | string[]);

And now there is no error anymore.

huangapple
  • 本文由 发表于 2023年3月7日 20:34:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/75662030.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定