Angular AOT元数据错误

2022-07-15 09:40 更新

AOT 元数据错误

以下是你可能会遇到的元数据错误,带有解释和建议的更正。

不支持表达形式 (Expression form not supported)

编译器在对 Angular 元数据求值时遇到了一个它不能理解的表达式。

如以下范例所示,使用了编译器的受限表达式语法之外的语言特性可能会产生此错误:

// ERROR
export class Fooish { … }
…
const prop = typeof Fooish; // typeof is not valid in metadata
  …
  // bracket notation is not valid in metadata
  { provide: 'token', useValue: { [prop]: 'value' } };
  …

你可以在普通的应用代码中使用 ​typeof ​和方括号标记法来指定属性名,但是这些特性不能在定义 Angular 元数据的表达式中使用。

通过在编写 Angular 元数据时坚持使用编译器的受限表达式语法来避免此错误,并小心新的或不常用的 TypeScript 功能。

引用本地(未导出的)符号 (Reference to a local (non-exported) symbol)

如果要引用局部(未导出的)符号 'symbol name',请考虑导出它。

编译器遇到了局部定义的未导出或未初始化的符号。

下面就是存在该问题的 ​provider ​范例。

// ERROR
let foo: number; // neither exported nor initialized

@Component({
  selector: 'my-component',
  template: … ,
  providers: [
    { provide: Foo, useValue: foo }
  ]
})
export class MyComponent {}

编译器会生成这个组件工厂,其中包含 ​useValue ​提供者的代码。那个工厂模块不能访问这个源码模块,无法访问这个(未导出的)​foo ​变量。

你可以通过初始化 ​foo ​来修正这个错误。

let foo = 42; // initialized

编译器会将表达式折叠到提供者中,就像你自己写的一样。

providers: [
  { provide: Foo, useValue: 42 }
]

另外,你也可以通过导出 ​foo ​来解决它,这样 ​foo ​将会在运行期间你真正知道它的值的时候被赋值。

// CORRECTED
export let foo: number; // exported

@Component({
  selector: 'my-component',
  template: … ,
  providers: [
    { provide: Foo, useValue: foo }
  ]
})
export class MyComponent {}

添加 ​export ​的方式通常用于需要在元数据中引用变量时,如 ​providers ​和 ​animations​,这样编译器就可以在这些表达式中生成对已导出变量的引用了。它不需要知道这些变量的

当编译器需要知道真正的值以生成代码时,添加 ​export ​的方式就是无效的。比如这里的 ​template ​属性。

// ERROR
export let someTemplate: string; // exported but not initialized

@Component({
  selector: 'my-component',
  template: someTemplate
})
export class MyComponent {}

编译器现在就需要 ​template ​属性的值来生成组件工厂。 仅仅有对该变量的引用是不够的。 给这个声明加上 ​export ​前缀只会生成一个新的错误 "​Only initialized variables and constants can be referenced【只能引用初始化过的变量和常量】"。

只支持初始化过的变量和常量 (Only initialized variables and constants)

只能引用已初始化过的变量和常量,因为模板编译器需要该变量的值。

编译器发现某个到已导出的变量或静态字段的引用是没有初始化过的。而它需要根据那个变量的值来生成代码。

下面的例子试图把组件的 ​template ​属性设置为已导出的 ​someTemplate ​变量的值,而这个值虽然声明过,却没有初始化过。

// ERROR
export let someTemplate: string;

@Component({
  selector: 'my-component',
  template: someTemplate
})
export class MyComponent {}

如果你从其它模块中导入了 ​someTemplate​,但那个模块中忘了初始化它,就会看到这个错误。

// ERROR - not initialized there either
import { someTemplate } from './config';

@Component({
  selector: 'my-component',
  template: someTemplate
})
export class MyComponent {}

编译器不能等到运行时才得到该模板的信息。它必须从源码中静态获得这个 ​someTemplate ​变量的值,以便生成组件工厂,组件工厂中需要包含根据这个模板来生成元素的代码。

要纠正这个错误,请在同一行的初始化子句中初始化这个变量的值。

// CORRECTED
export let someTemplate = '<h1>Greetings from Angular</h1>';

@Component({
  selector: 'my-component',
  template: someTemplate
})
export class MyComponent {}

引用未导出过的类 (Reference to a non-exported class)

对非导出类 ​<class name>​ 的引用。 考虑导出此类。

元数据引用了一个未导出的类。

比如,你可能定义了一个类并在某个 ​providers ​数组中把它用作了依赖注入令牌,但是忘了导出这个类。

// ERROR
abstract class MyStrategy { }

  …
  providers: [
    { provide: MyStrategy, useValue: … }
  ]
  …

Angular 在单独的模块中生成类工厂,并且该工厂只能访问导出的类。要更正此错误,请导出所引用的类。

// CORRECTED
export abstract class MyStrategy { }

  …
  providers: [
    { provide: MyStrategy, useValue: … }
  ]
  …

引用未导出过的函数 (Reference to a non-exported function)

元数据中引用了未导出的函数。

比如,你可能已经把某个服务提供者的 ​useFactory ​属性设置成了一个局部定义但忘了导出的函数。

// ERROR
function myStrategy() { … }

  …
  providers: [
    { provide: MyStrategy, useFactory: myStrategy }
  ]
  …

Angular 在单独的模块中生成类工厂,该工厂只能访问导出的函数。要更正此错误,请导出此函数。

// CORRECTED
export function myStrategy() { … }

  …
  providers: [
    { provide: MyStrategy, useFactory: myStrategy }
  ]
  …

不支持函数调用 (Function calls are not supported)

不支持函数调用。考虑把这个函数或 lambda 表达式替换成一个对已导出函数的引用。

编译器当前不支持函数表达式或 lambda 函数。比如,你不能将提供者的 ​useFactory ​设置为这样的匿名函数或箭头函数。

// ERROR
  …
  providers: [
    { provide: MyStrategy, useFactory: function() { … } },
    { provide: OtherStrategy, useFactory: () => { … } }
  ]
  …

如果你在某个提供者的 ​useValue ​中调用函数或方法,也会导致这个错误。

// ERROR
import { calculateValue } from './utilities';

  …
  providers: [
    { provide: SomeValue, useValue: calculateValue() }
  ]
  …

要改正这个问题,就要从模块中导出这个函数,并改成在服务提供者的 ​useFactory ​中引用该函数。

// CORRECTED
import { calculateValue } from './utilities';

export function myStrategy() { … }
export function otherStrategy() { … }
export function someValueFactory() {
  return calculateValue();
}
  …
  providers: [
    { provide: MyStrategy, useFactory: myStrategy },
    { provide: OtherStrategy, useFactory: otherStrategy },
    { provide: SomeValue, useFactory: someValueFactory }
  ]
  …

不支持解构变量或常量 (Destructured variable or constant not supported)

模板编译器不支持引用导出的解构语法的变量或常量。考虑简化这一点,以避免解构语法。

编译器不支持引用通过解构赋值的方式得到的变量。

比如,你不能这么写:

// ERROR
import { configuration } from './configuration';

// destructured assignment to foo and bar
const {foo, bar} = configuration;
  …
  providers: [
    {provide: Foo, useValue: foo},
    {provide: Bar, useValue: bar},
  ]
  …

要纠正这个错误,就要引用非解构方式的变量。

// CORRECTED
import { configuration } from './configuration';
  …
  providers: [
    {provide: Foo, useValue: configuration.foo},
    {provide: Bar, useValue: configuration.bar},
  ]
  …

无法解析类型 (Could not resolve type)

编译器遇到了某个类型,但是不知道它是由哪个模块导出的。

这通常会发生在你引用环境类型时。比如,​Window ​类型就是在全局 ​.d.ts​ 文件中声明的环境类型。

如果你在组件的构造函数中引用它就会导致一个错误,因为编译器必须对构造函数进行静态分析。

// ERROR
@Component({ })
export class MyComponent {
  constructor (private win: Window) { … }
}

TypeScript 能理解这些环境类型,所以你不用导入它们。但 Angular 编译器不理解你没有导入或导出过的类型。

这种情况下,编译器就无法理解如何使用这个 ​Window ​令牌来进行注入。

不要在元数据表达式中引用环境类型。

如果你必须注入某个环境类型的实例,可以用以下四步来巧妙解决这个问题:

  1. 为环境类型的实例创建一个注入令牌。
  2. 创建一个返回该实例的工厂函数。
  3. 使用该工厂函数添加一个 ​useFactory ​提供者。
  4. 使用 ​@Inject​ 来注入这个实例。

下面的例子说明了这一点。

// CORRECTED
import { Inject } from '@angular/core';

export const WINDOW = new InjectionToken('Window');
export function _window() { return window; }

@Component({
  …
  providers: [
    { provide: WINDOW, useFactory: _window }
  ]
})
export class MyComponent {
  constructor (@Inject(WINDOW) private win: Window) { … }
}

对于编译器来说,构造函数中出现 ​Window​ 类型已不再是个问题,因为它现在使用 ​@Inject(WINDOW)​ 来生成注入代码。

Angular 也用 ​DOCUMENT ​令牌做了类似的事情,所以你也可以注入浏览器的 ​document ​对象(或它的一个抽象层,取决于该应用运行在哪个平台)。

import { Inject }   from '@angular/core';
import { DOCUMENT } from '@angular/common';

@Component({ … })
export class MyComponent {
  constructor (@Inject(DOCUMENT) private doc: Document) { … }
}

期望的名字 (Name expected)

编译器在正在计算的表达式中期望有一个名字。

如果将数字用作属性名称,则可能发生这种情况,如以下范例所示。

// ERROR
provider: [{ provide: Foo, useValue: { 0: 'test' } }]

把该属性的名字改为非数字类型。

// CORRECTED
provider: [{ provide: Foo, useValue: { '0': 'test' } }]

不支持的枚举成员名称 (Unsupported enum member name)

Angular 不能确定你在元数据中引用的枚举成员的值。

编译器可以理解简单的枚举值,但不能理解复杂的,比如从那些计算属性中派生出来的。

// ERROR
enum Colors {
  Red = 1,
  White,
  Blue = "Blue".length // computed
}

  …
  providers: [
    { provide: BaseColor,   useValue: Colors.White } // ok
    { provide: DangerColor, useValue: Colors.Red }   // ok
    { provide: StrongColor, useValue: Colors.Blue }  // bad
  ]
  …

避免引用那些使用了复杂初始化对象或计算属性的枚举。

不支持带标签的模板表达式 (Tagged template expressions are not supported)

元数据中不支持带标签函数的模板表达式。

编译器遇到了 JavaScript ES2015 带标签的模板表达式,如下所示。

// ERROR
const expression = 'funky';
const raw = String.raw`A tagged template ${expression} string`;
 …
 template: '<div>' + raw + '</div>'
 …

String.raw()是 JavaScript ES2015 的原生标签函数

AOT 编译器不支持带标签函数的模板表达式,避免在元数据表达式中使用它们。

期待符号的引用 (Symbol reference expected)

编译器期待在错误信息指出的位置是一个符号引用。

当你在类的 ​extends ​子句中使用表达式时就会出现这个错误。


以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号