docs(aio): add Metadata guide based on Chuck’s docs
Chuck’s gist https://gist.github.com/chuckjaz/65dcc2fd5f4f5463e492ed0cb93bca60#file-Angular%20Metadata-md Also chuck’s doc on metadata-related errors (link needed)
This commit is contained in:
parent
d2c0d986d4
commit
9320f34f19
|
@ -0,0 +1,874 @@
|
|||
# Angular Metadata and AOT
|
||||
|
||||
The Angular **AOT compiler** turns your TypeScript source code into runnable JavaScript.
|
||||
As part of that process, the compiler extracts and interprets **metadata** about the parts of the application that Angular is supposed to manage.
|
||||
|
||||
You write metadata in a _subset_ of TypeScript. This guide explains why a subset is necessary, describes the subset constraints, and what happens when you step outside of those constraints.
|
||||
|
||||
## Angular metadata
|
||||
Angular metadata tells Angular how to construct instances of your application classes and interact with them at runtime.
|
||||
|
||||
You specify the metadata with **decorators** such as `@Component()` and `@Input()`.
|
||||
You also specify metadata implicitly in the constructor declarations of these decorated classes.
|
||||
|
||||
In the following example, the `@Component()` metadata object and the class constructor tell Angular how to create and display an instance of `TypicalComponent`.
|
||||
|
||||
```typescript
|
||||
@Component({
|
||||
selector: 'app-typical',
|
||||
template: 'div>A typical component for {{data.name}}</div>
|
||||
)}
|
||||
export class TypicalComponent {
|
||||
@Input() data: TypicalData;
|
||||
constructor(private someService: SomeService) { ... }
|
||||
}
|
||||
```
|
||||
|
||||
The Angular compiler extracts the metadata _once_ and generates a _factory_ for `TypicalComponent`.
|
||||
When it needs to create a `TypicalComponent` instance, Angular calls the factory, which produces a new visual element, bound to a new instance of the component class with its injected dependency.
|
||||
|
||||
## Compile ahead-of-time (AOT)
|
||||
|
||||
You should use AOT to compile an application that must launch quickly.
|
||||
With AOT, there is no runtime compile step.
|
||||
The client doesn't need the compiler library at all and excluding it significantly reduces the total payload.
|
||||
The browser downloads a smaller set of safely-compiled, application module(s) and libraries that it can parse quickly and run almost immediately.
|
||||
|
||||
The AOT compiler produces a number of files, including the application JavaScript that ultimately runs in the browser. It then statically analyzes your source code and interprets the Angular metadata without actually running the application.
|
||||
|
||||
To compile the app, run the `ngc` stand-alone tool as part of your build process.
|
||||
When using the CLI, run the `ng build` command.
|
||||
|
||||
For more information on AOT, see [Ahead-of-Time Compilation](guide/aot-compiler).
|
||||
|
||||
## Metadata restrictions
|
||||
|
||||
Angular metadata expressions must conform to the following general constraints:
|
||||
|
||||
1. Limit [expression syntax](#expression-syntax) to the supported subset of JavaScript.
|
||||
2. Only reference exported symbols after [code folding](#folding).
|
||||
3. Only call [functions supported](#supported-functions) by the compiler.
|
||||
4. Decorated and data-bound class members must be public.
|
||||
|
||||
The next sections elaborate on these points.
|
||||
|
||||
## How AOT works
|
||||
|
||||
It helps to think of the AOT compiler as having two phases: a code analysis phase in which it simply records a representation of the source; and a code generation phase in which the compiler's `StaticReflector` handles the interpretation as well as places restrictions on what it interprets.
|
||||
|
||||
## Phase 1: analysis
|
||||
|
||||
The TypeScript compiler does some of the analytic work of the first phase. It emits the `.d.ts` _type definition files_ with type information that the AOT compiler needs to generate application code.
|
||||
|
||||
At the same time, the AOT **_collector_** analyzes the metadata recorded in the Angular decorators and outputs metadata information in **`.metadata.json`** files, one per `.d.ts` file.
|
||||
|
||||
You can think of `.metadata.json` as a diagram of the overall structure of a decorator's metadata, represented as an [abstract syntax tree (AST)](https://en.wikipedia.org/wiki/Abstract_syntax_tree).
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
Angular's [schema.ts](https://github.com/angular/angular/blob/master/packages/tsc-wrapped/src/schema.ts)
|
||||
describes the JSON format as a collection of TypeScript interfaces.
|
||||
|
||||
</div>
|
||||
|
||||
{@a expression-syntax}
|
||||
### Expression syntax
|
||||
|
||||
The _collector_ only understands a subset of JavaScript.
|
||||
Define metadata objects with the following limited syntax:
|
||||
|
||||
Syntax | Example
|
||||
-----------------------------------|-----------------------------------
|
||||
Literal object | `{cherry: true, apple: true, mincemeat: false}`
|
||||
Literal array | `['cherries', 'flour', 'sugar']`
|
||||
Spread in literal array | `['apples', 'flour', ...the_rest]`
|
||||
Calls | `bake(ingredients)`
|
||||
New | `new Oven()`
|
||||
Property access | `pie.slice`
|
||||
Array index | `ingredients[0]`
|
||||
Identifier reference | `Component`
|
||||
A template string | <code>`pie is ${multiplier} times better than cake`</code>
|
||||
Literal string | `'pi'`
|
||||
Literal number | `3.14153265`
|
||||
Literal boolean | `true`
|
||||
Literal null | `null`
|
||||
Supported prefix operator | `!cake`
|
||||
Supported Binary operator | `a + b`
|
||||
Conditional operator | `a ? b : c`
|
||||
Parentheses | `(a + b)`
|
||||
|
||||
If an expression uses unsupported syntax, the _collector_ writes an error node to the `.metadata.json` file. The compiler later reports the error if it needs that
|
||||
piece of metadata to generate the application code.
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
If you want `ngc` to report syntax errors immediately rather than produce a `.metadata.json` file with errors, set the `strictMetadataEmit` option in `tsconfig`.
|
||||
|
||||
```
|
||||
"angularCompilerOptions": {
|
||||
...
|
||||
"strictMetadataEmit" : true
|
||||
}
|
||||
```
|
||||
|
||||
Angular libraries have this option to ensure that all Angular `.metadata.json` files are clean and it is a best practice to do the same when building your own libraries.
|
||||
|
||||
</div>
|
||||
|
||||
{@a function-expression}
|
||||
{@a arror-functions}
|
||||
### No arrow functions
|
||||
|
||||
The AOT compiler does not support [function expressions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/function)
|
||||
and [arrow functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions), also called _lambda_ functions.
|
||||
|
||||
Consider the following component decorator:
|
||||
|
||||
```ts
|
||||
@Component({
|
||||
...
|
||||
providers: [{provide: server, useFactory: () => new Server()}]
|
||||
})
|
||||
```
|
||||
|
||||
The AOT _collector_ does not support the arrow function, `() => new Server()`, in a metadata expression.
|
||||
It generates an error node in place of the function.
|
||||
|
||||
When the compiler later interprets this node, it reports an error that invites you to turn the arrow function into an _exported function_.
|
||||
|
||||
You can fix the error by converting to this:
|
||||
|
||||
```ts
|
||||
export function serverFactory() {
|
||||
return new Server();
|
||||
}
|
||||
|
||||
@Component({
|
||||
...
|
||||
providers: [{provide: server, useFactory: serverFactory}]
|
||||
})
|
||||
```
|
||||
|
||||
### Limited function calls
|
||||
|
||||
The _collector_ can represent a function call or object creation with `new` as long as the syntax is valid. The _collector_ only cares about proper syntax.
|
||||
|
||||
But beware. The compiler may later refuse to generate a call to a _particular_ function or creation of a _particular_ object.
|
||||
The compiler only supports calls to a small set of functions and will use `new` for only a few designated classes. These functions and classes are in a table of [below](#supported-functions).
|
||||
|
||||
|
||||
### Folding
|
||||
{@a exported-symbols}
|
||||
The compiler can only resolve references to **_exported_** symbols.
|
||||
Fortunately, the _collector_ enables limited use of non-exported symbols through _folding_.
|
||||
|
||||
The _collector_ may be able to evaluate an expression during collection and record the result in the `.metadata.json` instead of the original expression.
|
||||
|
||||
For example, the _collector_ can evaluate the expression `1 + 2 + 3 + 4` and replace it with the result, `10`.
|
||||
|
||||
This process is called _folding_. An expression that can be reduced in this manner is _foldable_.
|
||||
|
||||
{@a var-declaration}
|
||||
The collector can evaluate references to
|
||||
module-local `const` declarations and initialized `var` and `let` declarations, effectively removing them from the `.metadata.json` file.
|
||||
|
||||
Consider the following component definition:
|
||||
|
||||
```ts
|
||||
const template = '<div>{{hero.name}}</div>';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hero',
|
||||
template: template
|
||||
})
|
||||
class HeroComponent {
|
||||
@Input() hero: Hero;
|
||||
}
|
||||
```
|
||||
|
||||
The compiler could not refer to the `template` constant because it isn't exported.
|
||||
|
||||
But the _collector_ can _fold_ the `template` constant into the metadata definition by inlining its contents.
|
||||
The effect is the same as if you had written:
|
||||
|
||||
```TypeScript
|
||||
@Component({
|
||||
selector: 'app-hero',
|
||||
template: '<div>{{hero.name}}</div>'
|
||||
})
|
||||
class HeroComponent {
|
||||
@Input() hero: Hero;
|
||||
}
|
||||
```
|
||||
|
||||
There is no longer a reference to `template` and, therefore, nothing to trouble the compiler when it later interprets the _collector's_ output in `.metadata.json`.
|
||||
|
||||
You can take this example a step further by including the `template` constant in another expression:
|
||||
|
||||
```TypeScript
|
||||
const template = '<div>{{hero.name}}</div>';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hero',
|
||||
template: template + '<div>{{hero.title}}</div>'
|
||||
})
|
||||
class HeroComponent {
|
||||
@Input() hero: Hero;
|
||||
}
|
||||
```
|
||||
|
||||
The _collector_ reduces this expression to its equivalent _folded_ string:
|
||||
|
||||
`'<div>{{hero.name}}</div><div>{{hero.title}}</div>'`.
|
||||
|
||||
#### Foldable syntax
|
||||
|
||||
The following table describes which expressions the _collector_ can and cannot fold:
|
||||
|
||||
Syntax | Foldable
|
||||
-----------------------------------|-----------------------------------
|
||||
Literal object | yes
|
||||
Literal array | yes
|
||||
Spread in literal array | no
|
||||
Calls | no
|
||||
New | no
|
||||
Property access | yes, if target is foldable
|
||||
Array index | yes, if target and index are foldable
|
||||
Identifier reference | yes, if it is a reference to a local
|
||||
A template with no substitutions | yes
|
||||
A template with substitutions | yes, if the substitutions are foldable
|
||||
Literal string | yes
|
||||
Literal number | yes
|
||||
Literal boolean | yes
|
||||
Literal null | yes
|
||||
Supported prefix operator | yes, if operand is foldable
|
||||
Supported binary operator | yes, if both left and right are foldable
|
||||
Conditional operator | yes, if condition is foldable
|
||||
Parentheses | yes, if the expression is foldable
|
||||
|
||||
If an expression is not foldable, the collector writes it to `.metadata.json` as an [AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree) for the compiler to resolve.
|
||||
|
||||
|
||||
## Phase 2: code generation
|
||||
|
||||
The _collector_ makes no attempt to understand the metadata that it collects and outputs to `.metadata.json`. It represents the metadata as best it can and records errors when it detects a metadata syntax violation.
|
||||
|
||||
It's the compiler's job to interpret the `.metadata.json` in the code generation phase.
|
||||
|
||||
The compiler understands all syntax forms that the _collector_ supports, but it may reject _syntactically_ correct metadata if the _semantics_ violate compiler rules.
|
||||
|
||||
The compiler can only reference _exported symbols_.
|
||||
|
||||
Decorated component class members must be public. You cannot make an `@Input()` property private or internal.
|
||||
|
||||
Data bound properties must also be public.
|
||||
|
||||
```TypeScript
|
||||
// BAD CODE - title is private
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
template: '<h1>{{title}}</h1>'
|
||||
})
|
||||
class AppComponent {
|
||||
private title = 'My App'; // Bad
|
||||
}
|
||||
```
|
||||
|
||||
{@a supported-functions}
|
||||
Most importantly, the compiler only generates code to create instances of certain classes, support certain decorators, and call certain functions from the following lists.
|
||||
|
||||
|
||||
### New instances
|
||||
|
||||
The compiler only allows metadata that create instances of these Angular classes.
|
||||
|
||||
Class | Module
|
||||
-----------------|--------------
|
||||
`OpaqueToken` | `@angular/core`
|
||||
`InjectionToken` | `@angular/core`
|
||||
|
||||
|
||||
### Annotations/Decorators
|
||||
|
||||
The compiler only supports metadata for these Angular decorators.
|
||||
|
||||
Decorator | Module
|
||||
------------------|--------------
|
||||
`Attribute` | `@angular/core`
|
||||
`Component` | `@angular/core`
|
||||
`ContentChild` | `@angular/core`
|
||||
`ContentChildren` | `@angular/core`
|
||||
`Directive` | `@angular/core`
|
||||
`Host` | `@angular/core`
|
||||
`HostBinding` | `@angular/core`
|
||||
`HostListener` | `@angular/core`
|
||||
`Inject` | `@angular/core`
|
||||
`Injectable` | `@angular/core`
|
||||
`Input` | `@angular/core`
|
||||
`NgModule` | `@angular/core`
|
||||
`Optional` | `@angular/core`
|
||||
`Output` | `@angular/core`
|
||||
`Pipe` | `@angular/core`
|
||||
`Self` | `@angular/core`
|
||||
`SkipSelf` | `@angular/core`
|
||||
`ViewChild` | `@angular/core`
|
||||
|
||||
|
||||
### Macro-functions and macro-static methods
|
||||
|
||||
The compiler also supports _macros_ in the form of functions or static
|
||||
methods that return an expression.
|
||||
|
||||
For example, consider the following function:
|
||||
|
||||
```TypeScript
|
||||
export function wrapInArray<T>(value: T): T[] {
|
||||
return [value];
|
||||
}
|
||||
```
|
||||
|
||||
You can call the `wrapInArray` in a metadata definition because it returns the value of an expression that conforms to the compiler's restrictive JavaScript subset.
|
||||
|
||||
You might use `wrapInArray()` like this:
|
||||
|
||||
```TypeScript
|
||||
@NgModule({
|
||||
declarations: wrapInArray(TypicalComponent)
|
||||
})
|
||||
class TypicalModule {}
|
||||
```
|
||||
|
||||
The compiler treats this usage as if you had written:
|
||||
|
||||
```TypeScript
|
||||
@NgModule({
|
||||
declarations: [TypicalComponent]
|
||||
})
|
||||
class TypicalModule {}
|
||||
```
|
||||
|
||||
The collector is simplistic in its determination of what qualifies as a macro
|
||||
function; it can only contain a single `return` statement.
|
||||
|
||||
The Angular [`RouterModule`](api/router/RouterModule) exports two macro static methods, `forRoot` and `forChild`, to help declare root and child routes.
|
||||
Review the [source code](https://github.com/angular/angular/blob/master/packages/router/src/router_module.ts#L139 "RouterModule.forRoot source code")
|
||||
for these methods to see how macros can simplify configuration of complex Angular modules.
|
||||
|
||||
## Metadata Errors
|
||||
|
||||
The following are metadata errors you may encounter, with explanations and suggested corrections.
|
||||
|
||||
[Expression form not supported](#expression-form-not-supported)<br>
|
||||
[Reference to a local (non-exported) symbol](#reference-to-a-local-symbol)<br>
|
||||
[Only initialized variables and constants](#only-initialized-variables)<br>
|
||||
[Reference to a non-exported class](#reference-to-a-non-exported-class)<br>
|
||||
[Reference to a non-exported function](#reference-to-a-non-exported-function)<br>
|
||||
[Function calls are not supported](#function-calls-not-supported)<br>
|
||||
[Destructured variable or constant not supported](#destructured-variable-not-supported)<br>
|
||||
[Could not resolve type](#could-not-resolve-type)<br>
|
||||
[Name expected](#name-expected)<br>
|
||||
[Unsupported enum member name](#unsupported-enum-member-name)<br>
|
||||
[Tagged template expressions are not supported](#tagged-template-expressions-not-supported)<br>
|
||||
[Symbol reference expected](#symbol-reference-expected)<br>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3 class="no-toc">Expression form not supported</h3>
|
||||
|
||||
The compiler encountered an expression it didn't understand while evalutating Angular metadata.
|
||||
|
||||
Language features outside of the compiler's [restricted expression syntax](#expression-syntax)
|
||||
can produce this error, as seen in the following example:
|
||||
|
||||
```
|
||||
// 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' } };
|
||||
...
|
||||
```
|
||||
|
||||
You can use `typeof` and bracket notation in normal application code.
|
||||
You just can't use those features within expressions that define Angular metadata.
|
||||
|
||||
Avoid this error by sticking to the compiler's [restricted expression syntax](#expression-syntax)
|
||||
when writing Angular metadata
|
||||
and be wary of new or unusual TypeScript features.
|
||||
|
||||
<hr>
|
||||
|
||||
{@a reference-to-a-local-symbol}
|
||||
<h3 class="no-toc">Reference to a local (non-exported) symbol</h3>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
_Reference to a local (non-exported) symbol 'symbol name'. Consider exporting the symbol._
|
||||
|
||||
</div>
|
||||
|
||||
The compiler encountered a referenced to a locally defined symbol that either wasn't exported or wasn't initialized.
|
||||
|
||||
Here's a `provider` example of the problem.
|
||||
|
||||
```
|
||||
// ERROR
|
||||
let foo: number; // neither exported nor initialized
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: ... ,
|
||||
providers: [
|
||||
{ provide: Foo, useValue: foo }
|
||||
]
|
||||
})
|
||||
export class MyComponent {}
|
||||
```
|
||||
The compiler generates the component factory, which includes the `useValue` provider code, in a separate module. _That_ factory module can't reach back to _this_ source module to access the local (non-exported) `foo` variable.
|
||||
|
||||
You could fix the problem by initializing `foo`.
|
||||
|
||||
```
|
||||
let foo = 42; // initialized
|
||||
```
|
||||
|
||||
The compiler will [fold](#folding) the expression into the provider as if you had written this.
|
||||
|
||||
```
|
||||
providers: [
|
||||
{ provide: Foo, useValue: 42 }
|
||||
]
|
||||
```
|
||||
|
||||
Alternatively, you can fix it by exporting `foo` with the expectation that `foo` will be assigned at runtime when you actually know its value.
|
||||
|
||||
```
|
||||
// CORRECTED
|
||||
export let foo: number; // exported
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: ... ,
|
||||
providers: [
|
||||
{ provide: Foo, useValue: foo }
|
||||
]
|
||||
})
|
||||
export class MyComponent {}
|
||||
```
|
||||
|
||||
Adding `export` often works for variables referenced in metadata such as `providers` and `animations` because the compiler can generate _references_ to the exported variables in these expressions. It doesn't need the _values_ of those variables.
|
||||
|
||||
Adding `export` doesn't work when the compiler needs the _actual value_
|
||||
in order to generate code.
|
||||
For example, it doesn't work for the `template` property.
|
||||
|
||||
```
|
||||
// ERROR
|
||||
export let someTemplate: string; // exported but not initialized
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: someTemplate
|
||||
})
|
||||
export class MyComponent {}
|
||||
```
|
||||
|
||||
The compiler needs the value of the `template` property _right now_ to generate the component factory.
|
||||
The variable reference alone is insufficient.
|
||||
Prefixing the declaration with `export` merely produces a new error, "[`Only initialized variables and constants can be referenced`](#only-initialized-variables)".
|
||||
|
||||
<hr>
|
||||
|
||||
{@a only-initialized-variables}
|
||||
<h3 class="no-toc">Only initialized variables and constants</h3>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
_Only initialized variables and constants can be referenced because the value of this variable is needed by the template compiler._
|
||||
|
||||
</div>
|
||||
|
||||
The compiler found a reference to an exported variable or static field that wasn't initialized.
|
||||
It needs the value of that variable to generate code.
|
||||
|
||||
The following example tries to set the component's `template` property to the value of
|
||||
the exported `someTemplate` variable which is declared but _unassigned_.
|
||||
|
||||
```
|
||||
// ERROR
|
||||
export let someTemplate: string;
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: someTemplate
|
||||
})
|
||||
export class MyComponent {}
|
||||
```
|
||||
|
||||
You'd also get this error if you imported `someTemplate` from some other module and neglected to initialize it there.
|
||||
|
||||
```
|
||||
// ERROR - not initialized there either
|
||||
import { someTemplate } from './config';
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: someTemplate
|
||||
})
|
||||
export class MyComponent {}
|
||||
```
|
||||
|
||||
The compiler cannot wait until runtime to get the template information.
|
||||
It must statically derive the value of the `someTemplate` variable from the source code
|
||||
so that it can generate the component factory, which includes
|
||||
instructions for building the element based on the template.
|
||||
|
||||
To correct this error, provide the initial value of the variable in an initializer clause _on the same line_.
|
||||
|
||||
```
|
||||
// CORRECTED
|
||||
export let someTemplate = '<h1>Greetings from Angular</h1>';
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: someTemplate
|
||||
})
|
||||
export class MyComponent {}
|
||||
```
|
||||
|
||||
<hr>
|
||||
|
||||
<h3 class="no-toc">Reference to a non-exported class</h3>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
_Reference to a non-exported class <class name>. Consider exporting the class._
|
||||
|
||||
</div>
|
||||
|
||||
Metadata referenced a class that wasn't exported.
|
||||
|
||||
For example, you may have defined a class and used it as an injection token in a providers array
|
||||
but neglected to export that class.
|
||||
|
||||
```
|
||||
// ERROR
|
||||
abstract class MyStrategy { }
|
||||
|
||||
...
|
||||
providers: [
|
||||
{ provide: MyStrategy, useValue: ... }
|
||||
]
|
||||
...
|
||||
```
|
||||
|
||||
Angular generates a class factory in a separate module and that
|
||||
factory [can only access exported classes](#exported-symbols).
|
||||
To correct this error, export the referenced class.
|
||||
|
||||
```
|
||||
// CORRECTED
|
||||
export abstract class MyStrategy { }
|
||||
|
||||
...
|
||||
providers: [
|
||||
{ provide: MyStrategy, useValue: ... }
|
||||
]
|
||||
...
|
||||
```
|
||||
<hr>
|
||||
|
||||
<h3 class="no-toc">Reference to a non-exported function</h3>
|
||||
|
||||
Metadata referenced a function that wasn't exported.
|
||||
|
||||
For example, you may have set a providers `useFactory` property to a locally defined function that you neglected to export.
|
||||
|
||||
```
|
||||
// ERROR
|
||||
function myStrategy() { ... }
|
||||
|
||||
...
|
||||
providers: [
|
||||
{ provide: MyStrategy, useFactory: myStrategy }
|
||||
]
|
||||
...
|
||||
```
|
||||
|
||||
Angular generates a class factory in a separate module and that
|
||||
factory [can only access exported functions](#exported-symbols).
|
||||
To correct this error, export the function.
|
||||
|
||||
```
|
||||
// CORRECTED
|
||||
export function myStrategy() { ... }
|
||||
|
||||
...
|
||||
providers: [
|
||||
{ provide: MyStrategy, useFactory: myStrategy }
|
||||
]
|
||||
...
|
||||
```
|
||||
<hr>
|
||||
|
||||
{@a function-calls-not-supported}
|
||||
<h3 class="no-toc">Function calls are not supported</h3>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
_Function calls are not supported. Consider replacing the function or lambda with a reference to an exported function._
|
||||
|
||||
</div>
|
||||
|
||||
The compiler does not currently support [function expressions or lambda functions](#function-expression).
|
||||
For example, you cannot set a provider's `useFactory` to an anonymous function or arrow function like this.
|
||||
|
||||
```
|
||||
// ERROR
|
||||
...
|
||||
providers: [
|
||||
{ provide: MyStrategy, useFactory: function() { ... } },
|
||||
{ provide: OtherStrategy, useFactory: () => { ... } }
|
||||
]
|
||||
...
|
||||
```
|
||||
You also get this error if you call a function or method in a provider's `useValue`.
|
||||
```
|
||||
// ERROR
|
||||
import { calculateValue } from './utilities';
|
||||
|
||||
...
|
||||
providers: [
|
||||
{ provide: SomeValue, useValue: calculateValue() }
|
||||
]
|
||||
...
|
||||
```
|
||||
|
||||
To correct this error, export a function from the module and refer to the function in a `useFactory` provider instead.
|
||||
|
||||
<code-example linenums="false">
|
||||
// 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 }
|
||||
]
|
||||
...
|
||||
</code-example>
|
||||
|
||||
<hr>
|
||||
|
||||
{@a destructured-variable-not-supported}
|
||||
<h3 class="no-toc">Destructured variable or constant not supported</h3>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
_Referencing an exported destructured variable or constant is not supported by the template compiler. Consider simplifying this to avoid destructuring._
|
||||
|
||||
</div>
|
||||
|
||||
The compiler does not support references to variables assigned by [destructuring](https://www.typescriptlang.org/docs/handbook/variable-declarations.html#destructuring).
|
||||
|
||||
For example, you cannot write something like this:
|
||||
|
||||
<code-example linenums="false">
|
||||
// ERROR
|
||||
import { configuration } from './configuration';
|
||||
|
||||
// destructured assignment to foo and bar
|
||||
const {foo, bar} = configuration;
|
||||
...
|
||||
providers: [
|
||||
{provide: Foo, useValue: foo},
|
||||
{provide: Bar, useValue: bar},
|
||||
]
|
||||
...
|
||||
</code-example>
|
||||
|
||||
To correct this error, refer to non-destructured values.
|
||||
|
||||
<code-example linenums="false">
|
||||
// CORRECTED
|
||||
import { configuration } from './configuration';
|
||||
...
|
||||
providers: [
|
||||
{provide: Foo, useValue: configuration.foo},
|
||||
{provide: Bar, useValue: configuration.bar},
|
||||
]
|
||||
...
|
||||
</code-example>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3 class="no-toc">Could not resolve type</h3>
|
||||
|
||||
The compiler encountered a type and can't determine which module exports that type.
|
||||
|
||||
This can happen if you refer to an ambient type.
|
||||
For example, the `Window` type is an ambiant type declared in the global `.d.ts` file.
|
||||
|
||||
You'll get an error if you reference it in the component constructor,
|
||||
which the compiler must statically analyze.
|
||||
|
||||
```
|
||||
// ERROR
|
||||
@Component({ })
|
||||
export class MyComponent {
|
||||
constructor (private win: Window) { ... }
|
||||
}
|
||||
```
|
||||
TypeScript understands ambiant types so you don't import them.
|
||||
The Angular compiler does not understand a type that you neglect to export or import.
|
||||
|
||||
In this case, the compiler doesn't understand how to inject something with the `Window` token.
|
||||
|
||||
Do not refer to ambient types in metadata expressions.
|
||||
|
||||
If you must inject an instance of an ambiant type,
|
||||
you can finesse the problem in four steps:
|
||||
|
||||
1. Create an injection token for an instance of the ambiant type.
|
||||
1. Create a factory function that returns that instance.
|
||||
1. Add a `useFactory` provider with that factory function.
|
||||
1. Use `@Inject` to inject the instance.
|
||||
|
||||
Here's an illustrative example.
|
||||
|
||||
<code-example linenums="false">
|
||||
// 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) { ... }
|
||||
}
|
||||
</code-example>
|
||||
|
||||
The `Window` type in the constructor is no longer a problem for the compiler because it
|
||||
uses the `@Inject(WINDOW)` to generate the injection code.
|
||||
|
||||
Angular does something similar with the `DOCUMENT` token so you can inject the browser's `document` object (or an abstraction of it, depending upon the platform in which the application runs).
|
||||
|
||||
<code-example linenums="false">
|
||||
import { Inject } from '@angular/core';
|
||||
import { DOCUMENT } from '@angular/platform-browser';
|
||||
|
||||
@Component({ ... })
|
||||
export class MyComponent {
|
||||
constructor (@Inject(DOCUMENT) private doc: Document) { ... }
|
||||
}
|
||||
</code-example>
|
||||
<hr>
|
||||
|
||||
<h3 class="no-toc">Name expected</h3>
|
||||
|
||||
The compiler expected a name in an expression it was evaluating.
|
||||
This can happen if you use a number as a property name as in the following example.
|
||||
|
||||
```
|
||||
// ERROR
|
||||
provider: [{ provide: Foo, useValue: { 0: 'test' } }]
|
||||
```
|
||||
|
||||
Change the name of the property to something non-numeric.
|
||||
|
||||
```
|
||||
// CORRECTED
|
||||
provider: [{ provide: Foo, useValue: { '0': 'test' } }]
|
||||
```
|
||||
|
||||
<hr>
|
||||
|
||||
<h3 class="no-toc">Unsupported enum member name</h3>
|
||||
|
||||
Angular couldn't determine the value of the [enum member](https://www.typescriptlang.org/docs/handbook/enums.html)
|
||||
that you referenced in metadata.
|
||||
|
||||
The compiler can understand simple enum values but not complex values such as those derived from computed properties.
|
||||
|
||||
<code-example linenums="false">
|
||||
// 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
|
||||
]
|
||||
...
|
||||
</code-example>
|
||||
|
||||
Avoid referring to enums with complicated initializers or computed properties.
|
||||
|
||||
<hr>
|
||||
|
||||
{@a tagged-template-expressions-not-supported}
|
||||
<h3 class="no-toc">Tagged template expressions are not supported</h3>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
_Tagged template expressions are not supported in metadata._
|
||||
|
||||
</div>
|
||||
|
||||
The compiler encountered a JavaScript ES2015 [tagged template expression](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Tagged_template_literals) such as,
|
||||
```
|
||||
// ERROR
|
||||
const expression = 'funky';
|
||||
const raw = String.raw`A tagged template ${expression} string`;
|
||||
...
|
||||
template: '<div>' + raw + '</div>'
|
||||
...
|
||||
```
|
||||
[`String.raw()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/raw)
|
||||
is a _tag function_ native to JavaScript ES2015.
|
||||
|
||||
The AOT compiler does not support tagged template expressions; avoid them in metadata expressions.
|
||||
|
||||
<hr>
|
||||
|
||||
<h3 class="no-toc">Symbol reference expected</h3>
|
||||
|
||||
The compiler expected a reference to a symbol at the location specified in the error message.
|
||||
|
||||
This error can occur if you use an expression in the `extends` clause of a class.
|
||||
|
||||
<!--
|
||||
|
||||
Chuck: After reviewing your PR comment I'm still at a loss. See [comment there](https://github.com/angular/angular/pull/17712#discussion_r132025495).
|
||||
|
||||
-->
|
||||
|
||||
## Conclusion
|
||||
|
||||
This page covered:
|
||||
|
||||
* What the AOT compiler does.
|
||||
* Why metadata must be written in a subset of JavaScript.
|
||||
* What that subset is.
|
||||
* Other restrictions on metadata definition.
|
||||
* Macro-functions and macro-static methods.
|
||||
* Compiler errors related to metadata.
|
|
@ -338,6 +338,11 @@
|
|||
"title": "Ahead-of-Time Compilation",
|
||||
"tooltip": "Learn why and how to use the Ahead-of-Time (AOT) compiler."
|
||||
},
|
||||
{
|
||||
"url": "guide/metadata",
|
||||
"title": "Metadata and AOT",
|
||||
"tooltip": "How Angular handles metadata with AOT."
|
||||
},
|
||||
{
|
||||
"url": "guide/deployment",
|
||||
"title": "Deployment",
|
||||
|
|
Loading…
Reference in New Issue