【问题标题】:Is there a way to add a type assertion / annotation to a template input variable?有没有办法向模板输入变量添加类型断言/注释?
【发布时间】:2018-08-29 22:59:51
【问题描述】:

背景

我有一个看起来像这样的模板(我正在使用一些组件,它使用它作为重复项的基础,它是 <p-pickList>,但问题并不特定于该组件,仅作为示例)

对于背景,假设我有一个类型 Foo 并且我的组件有一个 foos: Foo[],我将它提供给 [source] 属性下的 <p-pickList> 组件,它执行内部 *ngFor 为它,我所要做的就是提供一个模板

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

但是,foo 上的类型信息似乎丢失了。

我是类型安全的忠实拥护者,我喜欢 Intellij(或任何其他编辑器)如果在模板中执行诸如指定 foo 的无效属性之类的操作,可以向我显示警告

如果我有一个普通的*ngFor,它会推断出foo的类型

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

问题:

  1. 是否有任何语法可以让我提示let-foo 的类型? (希望大多数 IDE 都能识别)。

  2. 如果我不想依赖 IDE,有没有办法让 ng 编译器类型检查 foo(由 let-foo 声明)?

tl;dr 是否有让我键入注释模板输入变量的语法? 例如类似这样的语法?

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

解决方法

一个愚蠢的想法是在我的组件中有一个标识函数,例如

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

但是做

{{ identity(foo).fooProp }}

相对于

来说进步不大
{{ (foo as Foo).fooProp }}`

【问题讨论】:

  • 不确定fulltemplatetypecheck 选项是否有效? angular.io/guide/aot-compiler#fulltemplatetypecheck
  • 可能是接收类型参数的管道?像 {{ youwhareverData | typeChecker:FooClass }}
  • 这更像是您需要提交给 Angular 团队的功能请求
  • 这能回答你的问题吗? ng-template - typed variable
  • 即使使用 fullTemplateTypeCheck 和 strictTemplates,如果参数不存在,Prod 构建也无法识别

标签: angular angular6


【解决方案1】:

让我们看看 Angular 与它有什么相似之处以及它是如何工作的!

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

我们可以在没有*魔法的情况下重写它

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

没有观察者 (ngDoCheck) 可以与 (但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>

或者我们可以自己创建

// 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

模板类型检查魔法发生在viewContainerRef.createEmbeddedView 中。 (例如ngFor); 但它假设 templateRef 接受什么。

Angular 可以编译在AOT:

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

据我了解,我们应该假设模板具有哪些类型,但要检查模板何时被实例化(createEmbeddedView);

【讨论】:

    【解决方案2】:

    问题是没有任何类型信息。 &lt;ng-template let-foo pTemplate="item"&gt; - 只是模板声明,它还没有绑定到任何类型/上下文,只是一些具有或可能具有 anythingGoesHereWithNoWarningAndNoAutocomplete 字段的动态类型。如果没有适当的泛型支持,就不可能拥有这种类型安全。

    【讨论】:

      【解决方案3】:

      发现了这个问题并提出了允许模板输入变量发布类型信息的解决方案,以便 IDE 可以在 github here 上提供更好的补全。此功能目前正在开发中,希望它会在 Angular 的未来版本中推出。你现在应该在 VS Code 或 Sublime Text 中使用 Angular Language Service 扩展名。您可以放心扩展的支持,因为它正在由 Angular 团队开发。欲了解更多信息,请查看此readmedocs

      正如你提到的,它可以通过使用标识函数来实现,但它会导致严重的性能问题,并且为不同类型维护此功能会很麻烦。

      【讨论】:

        【解决方案4】:

        这可以通过将变量包装在另一个 ng-template

        中来解决

        这在某种程度上是另一种解决方法,但我比这里的其他解决方案更喜欢它,因为它只是在 HTML 中增加了 2 行代码,当然如果你只使用你的变量 1 或 2 次 this other solution更好。我的回答:

        而不是这个:

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

        这样做:

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

        正如大家已经说过的,在使用*ngFor 时,IDE 会注意到类型断言。它在使用*ngIf 时也有效。当然,这种解决方案有一个缺点,因为内部的 &lt;ng-template&gt; 会因为 [ngIf] 而在稍后呈现。

        有了这个,现在如果你在你的上下文中添加一个无效的属性,你会得到以下编译错误,这很好,here's a stackblitz demo

        类型“Foo”上不存在属性“newFooProp”。

        我通过阅读documentation here 想出了这个解决方案:

        我测试了这个解决方案,并使用带有angularCompilerOptions 属性enableIvyfullTemplateTypeCheck 设置为true 的vscode 在Angular 11 上工作。

        另外,应该安装Angular Language Service,即使安装了ALS,也需要这个解决方案,因为模板可以从任何地方调用,所以ALS没有办法知道正在传递的上下文参数.这可以在我添加的stackblitz demo 中轻松测试,只需将fooProp 重命名为fooProp1,您会看到未键入的示例显示一个空值,而键入的则抛出错误。

        【讨论】:

        • 我相信您的意思是&lt;ng-template [ngIf]="identity(untypedArgs)" let-args="ngIf"&gt;,但即使拥有正确的angularCompilerOptions,该变量在我的测试中仍被视为any
        • 这个解决方案不是hack,它只是利用[ngIf]的预期行为,所以如果你发现它在你的测试中不起作用的原因,请告诉我们!跨度>
        • @Mart,我已将代码修复为identity(untypedArgs),谢谢。另外,我添加了对 Angular 语言服务的引用,你安装了那个插件吗? marketplace.visualstudio.com/items?itemName=Angular.ng-template
        • 是的,我有 Angular 语言服务插件,这个技巧适用于一个新的简单项目。我必须弄清楚为什么我的大型项目的行为方式不同。
        【解决方案5】:

        VSCode 应该具有开箱即用的功能,刚刚在 stackblitz 中进行了测试:https://stackblitz.com/edit/angular-type-assertion?file=src/app/app.component.html

        【讨论】:

        • 您确定它没有使用扩展程序吗?我在 Windows 10 上的 VS Code 没有这个。我知道这个扩展可以做 HTML 智能感知:marketplace.visualstudio.com/items?itemName=Angular.ng-template
        • 嘿博兰!在这个示例中,我使用了 stackblitz 在线的 VSCode,我找不到任何关于他们在 VSCode 中使用的任何插件的文档
        • Stackblitz 使用Angular Language Service
        • @hamilton.lima 这个问题是关于模板输入变量的类型断言/注释。即使在 Stackblitz 上,对模板输入变量中对象类型的引用也会丢失。
        • 您没有在代码中检查模板输入变量。在此处更新了您的 Stackblitz:stackblitz.com/edit/angular-type-assertion-wndgva?file=src/app/… 检查类型信息是否在 ng-template let-foo 中丢失了@
        猜你喜欢
        • 2021-11-18
        • 2012-11-03
        • 2023-03-16
        • 2022-06-15
        • 1970-01-01
        • 2020-03-26
        • 2021-10-31
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多