2018-04-24 11:34:11 -07:00
|
|
|
/**
|
|
|
|
* @license
|
|
|
|
* Copyright Google Inc. All Rights Reserved.
|
|
|
|
*
|
|
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
|
|
* found in the LICENSE file at https://angular.io/license
|
|
|
|
*/
|
|
|
|
|
|
|
|
import {StaticSymbol} from '../aot/static_symbol';
|
|
|
|
import {CompileTypeMetadata, tokenReference} from '../compile_metadata';
|
|
|
|
import {CompileReflector} from '../compile_reflector';
|
|
|
|
import {InjectFlags} from '../core';
|
|
|
|
import {Identifiers} from '../identifiers';
|
|
|
|
import * as o from '../output/output_ast';
|
|
|
|
import {Identifiers as R3} from '../render3/r3_identifiers';
|
|
|
|
import {OutputContext} from '../util';
|
|
|
|
|
2019-08-12 09:26:20 +03:00
|
|
|
import {typeWithParameters} from './util';
|
2018-10-10 15:53:14 +02:00
|
|
|
import {unsupported} from './view/util';
|
2018-07-16 16:36:31 -07:00
|
|
|
|
2018-04-24 11:34:11 -07:00
|
|
|
|
2019-08-12 09:26:20 +03:00
|
|
|
|
2018-04-24 11:34:11 -07:00
|
|
|
/**
|
|
|
|
* Metadata required by the factory generator to generate a `factory` function for a type.
|
|
|
|
*/
|
2018-07-16 16:36:31 -07:00
|
|
|
export interface R3ConstructorFactoryMetadata {
|
2018-04-24 11:34:11 -07:00
|
|
|
/**
|
|
|
|
* String name of the type being generated (used to name the factory function).
|
|
|
|
*/
|
|
|
|
name: string;
|
|
|
|
|
|
|
|
/**
|
refactor(ivy): split `type` into `type`, `internalType` and `adjacentType` (#33533)
When compiling an Angular decorator (e.g. Directive), @angular/compiler
generates an 'expression' to be added as a static definition field
on the class, a 'type' which will be added for that field to the .d.ts
file, and a statement adjacent to the class that calls `setClassMetadata()`.
Previously, the same WrappedNodeExpr of the class' ts.Identifier was used
within each of this situations.
In the ngtsc case, this is proper. In the ngcc case, if the class being
compiled is within an ES5 IIFE, the outer name of the class may have
changed. Thus, the class has both an inner and outer name. The outer name
should continue to be used elsewhere in the compiler and in 'type'.
The 'expression' will live within the IIFE, the `internalType` should be used.
The adjacent statement will also live within the IIFE, the `adjacentType` should be used.
This commit introduces `ReflectionHost.getInternalNameOfClass()` and
`ReflectionHost.getAdjacentNameOfClass()`, which the compiler can use to
query for the correct name to use.
PR Close #33533
2019-11-01 16:55:09 +00:00
|
|
|
* An expression representing the interface type being constructed.
|
2018-04-24 11:34:11 -07:00
|
|
|
*/
|
2018-07-16 16:36:31 -07:00
|
|
|
type: o.Expression;
|
2018-04-24 11:34:11 -07:00
|
|
|
|
refactor(ivy): split `type` into `type`, `internalType` and `adjacentType` (#33533)
When compiling an Angular decorator (e.g. Directive), @angular/compiler
generates an 'expression' to be added as a static definition field
on the class, a 'type' which will be added for that field to the .d.ts
file, and a statement adjacent to the class that calls `setClassMetadata()`.
Previously, the same WrappedNodeExpr of the class' ts.Identifier was used
within each of this situations.
In the ngtsc case, this is proper. In the ngcc case, if the class being
compiled is within an ES5 IIFE, the outer name of the class may have
changed. Thus, the class has both an inner and outer name. The outer name
should continue to be used elsewhere in the compiler and in 'type'.
The 'expression' will live within the IIFE, the `internalType` should be used.
The adjacent statement will also live within the IIFE, the `adjacentType` should be used.
This commit introduces `ReflectionHost.getInternalNameOfClass()` and
`ReflectionHost.getAdjacentNameOfClass()`, which the compiler can use to
query for the correct name to use.
PR Close #33533
2019-11-01 16:55:09 +00:00
|
|
|
/**
|
|
|
|
* An expression representing the constructor type, intended for use within a class definition
|
|
|
|
* itself.
|
|
|
|
*
|
|
|
|
* This can differ from the outer `type` if the class is being compiled by ngcc and is inside
|
|
|
|
* an IIFE structure that uses a different name internally.
|
|
|
|
*/
|
|
|
|
internalType: o.Expression;
|
|
|
|
|
2019-08-12 09:26:20 +03:00
|
|
|
/** Number of arguments for the `type`. */
|
|
|
|
typeArgumentCount: number;
|
|
|
|
|
2018-04-24 11:34:11 -07:00
|
|
|
/**
|
|
|
|
* Regardless of whether `fnOrClass` is a constructor function or a user-defined factory, it
|
|
|
|
* may have 0 or more parameters, which will be injected according to the `R3DependencyMetadata`
|
2018-07-16 16:36:31 -07:00
|
|
|
* for those parameters. If this is `null`, then the type's constructor is nonexistent and will
|
feat(ivy): compile @Injectable on classes not meant for DI (#28523)
In the past, @Injectable had no side effects and existing Angular code is
therefore littered with @Injectable usage on classes which are not intended
to be injected.
A common example is:
@Injectable()
class Foo {
constructor(private notInjectable: string) {}
}
and somewhere else:
providers: [{provide: Foo, useFactory: ...})
Here, there is no need for Foo to be injectable - indeed, it's impossible
for the DI system to create an instance of it, as it has a non-injectable
constructor. The provider configures a factory for the DI system to be
able to create instances of Foo.
Adding @Injectable in Ivy signifies that the class's own constructor, and
not a provider, determines how the class will be created.
This commit adds logic to compile classes which are marked with @Injectable
but are otherwise not injectable, and create an ngInjectableDef field with
a factory function that throws an error. This way, existing code in the wild
continues to compile, but if someone attempts to use the injectable it will
fail with a useful error message.
In the case where strictInjectionParameters is set to true, a compile-time
error is thrown instead of the runtime error, as ngtsc has enough
information to determine when injection couldn't possibly be valid.
PR Close #28523
2019-01-31 14:23:54 -08:00
|
|
|
* be inherited from `fnOrClass` which is interpreted as the current type. If this is `'invalid'`,
|
|
|
|
* then one or more of the parameters wasn't resolvable and any attempt to use these deps will
|
|
|
|
* result in a runtime error.
|
2018-04-24 11:34:11 -07:00
|
|
|
*/
|
feat(ivy): compile @Injectable on classes not meant for DI (#28523)
In the past, @Injectable had no side effects and existing Angular code is
therefore littered with @Injectable usage on classes which are not intended
to be injected.
A common example is:
@Injectable()
class Foo {
constructor(private notInjectable: string) {}
}
and somewhere else:
providers: [{provide: Foo, useFactory: ...})
Here, there is no need for Foo to be injectable - indeed, it's impossible
for the DI system to create an instance of it, as it has a non-injectable
constructor. The provider configures a factory for the DI system to be
able to create instances of Foo.
Adding @Injectable in Ivy signifies that the class's own constructor, and
not a provider, determines how the class will be created.
This commit adds logic to compile classes which are marked with @Injectable
but are otherwise not injectable, and create an ngInjectableDef field with
a factory function that throws an error. This way, existing code in the wild
continues to compile, but if someone attempts to use the injectable it will
fail with a useful error message.
In the case where strictInjectionParameters is set to true, a compile-time
error is thrown instead of the runtime error, as ngtsc has enough
information to determine when injection couldn't possibly be valid.
PR Close #28523
2019-01-31 14:23:54 -08:00
|
|
|
deps: R3DependencyMetadata[]|'invalid'|null;
|
2018-04-24 11:34:11 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* An expression for the function which will be used to inject dependencies. The API of this
|
|
|
|
* function could be different, and other options control how it will be invoked.
|
|
|
|
*/
|
|
|
|
injectFn: o.ExternalReference;
|
2019-10-03 21:54:49 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Type of the target being created by the factory.
|
|
|
|
*/
|
|
|
|
target: R3FactoryTarget;
|
2018-04-24 11:34:11 -07:00
|
|
|
}
|
|
|
|
|
2018-07-16 16:36:31 -07:00
|
|
|
export enum R3FactoryDelegateType {
|
|
|
|
Class,
|
|
|
|
Function,
|
|
|
|
Factory,
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface R3DelegatedFactoryMetadata extends R3ConstructorFactoryMetadata {
|
|
|
|
delegate: o.Expression;
|
|
|
|
delegateType: R3FactoryDelegateType.Factory;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface R3DelegatedFnOrClassMetadata extends R3ConstructorFactoryMetadata {
|
|
|
|
delegate: o.Expression;
|
|
|
|
delegateType: R3FactoryDelegateType.Class|R3FactoryDelegateType.Function;
|
|
|
|
delegateDeps: R3DependencyMetadata[];
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface R3ExpressionFactoryMetadata extends R3ConstructorFactoryMetadata {
|
|
|
|
expression: o.Expression;
|
|
|
|
}
|
|
|
|
|
|
|
|
export type R3FactoryMetadata = R3ConstructorFactoryMetadata | R3DelegatedFactoryMetadata |
|
|
|
|
R3DelegatedFnOrClassMetadata | R3ExpressionFactoryMetadata;
|
|
|
|
|
2019-10-03 21:54:49 +02:00
|
|
|
export enum R3FactoryTarget {
|
|
|
|
Directive = 0,
|
|
|
|
Component = 1,
|
|
|
|
Injectable = 2,
|
|
|
|
Pipe = 3,
|
|
|
|
NgModule = 4,
|
2019-08-12 09:26:20 +03:00
|
|
|
}
|
|
|
|
|
2018-04-24 11:34:11 -07:00
|
|
|
/**
|
|
|
|
* Resolved type of a dependency.
|
|
|
|
*
|
|
|
|
* Occasionally, dependencies will have special significance which is known statically. In that
|
|
|
|
* case the `R3ResolvedDependencyType` informs the factory generator that a particular dependency
|
|
|
|
* should be generated specially (usually by calling a special injection function instead of the
|
|
|
|
* standard one).
|
|
|
|
*/
|
|
|
|
export enum R3ResolvedDependencyType {
|
|
|
|
/**
|
|
|
|
* A normal token dependency.
|
|
|
|
*/
|
|
|
|
Token = 0,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The dependency is for an attribute.
|
|
|
|
*
|
|
|
|
* The token expression is a string representing the attribute name.
|
|
|
|
*/
|
|
|
|
Attribute = 1,
|
2019-07-12 20:15:12 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Injecting the `ChangeDetectorRef` token. Needs special handling when injected into a pipe.
|
|
|
|
*/
|
|
|
|
ChangeDetectorRef = 2,
|
2018-04-24 11:34:11 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Metadata representing a single dependency to be injected into a constructor or function call.
|
|
|
|
*/
|
|
|
|
export interface R3DependencyMetadata {
|
|
|
|
/**
|
|
|
|
* An expression representing the token or value to be injected.
|
|
|
|
*/
|
|
|
|
token: o.Expression;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* An enum indicating whether this dependency has special meaning to Angular and needs to be
|
|
|
|
* injected specially.
|
|
|
|
*/
|
|
|
|
resolved: R3ResolvedDependencyType;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Whether the dependency has an @Host qualifier.
|
|
|
|
*/
|
|
|
|
host: boolean;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Whether the dependency has an @Optional qualifier.
|
|
|
|
*/
|
|
|
|
optional: boolean;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Whether the dependency has an @Self qualifier.
|
|
|
|
*/
|
|
|
|
self: boolean;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Whether the dependency has an @SkipSelf qualifier.
|
|
|
|
*/
|
|
|
|
skipSelf: boolean;
|
|
|
|
}
|
|
|
|
|
2019-08-12 09:26:20 +03:00
|
|
|
export interface R3FactoryFn {
|
|
|
|
factory: o.Expression;
|
|
|
|
statements: o.Statement[];
|
|
|
|
type: o.ExpressionType;
|
|
|
|
}
|
|
|
|
|
2018-04-24 11:34:11 -07:00
|
|
|
/**
|
|
|
|
* Construct a factory function expression for the given `R3FactoryMetadata`.
|
|
|
|
*/
|
2019-10-03 21:54:49 +02:00
|
|
|
export function compileFactoryFunction(meta: R3FactoryMetadata): R3FactoryFn {
|
2018-07-16 16:36:31 -07:00
|
|
|
const t = o.variable('t');
|
|
|
|
const statements: o.Statement[] = [];
|
|
|
|
|
|
|
|
// The type to instantiate via constructor invocation. If there is no delegated factory, meaning
|
|
|
|
// this type is always created by constructor invocation, then this is the type-to-create
|
|
|
|
// parameter provided by the user (t) if specified, or the current type if not. If there is a
|
|
|
|
// delegated factory (which is used to create the current type) then this is only the type-to-
|
|
|
|
// create parameter (t).
|
refactor(ivy): split `type` into `type`, `internalType` and `adjacentType` (#33533)
When compiling an Angular decorator (e.g. Directive), @angular/compiler
generates an 'expression' to be added as a static definition field
on the class, a 'type' which will be added for that field to the .d.ts
file, and a statement adjacent to the class that calls `setClassMetadata()`.
Previously, the same WrappedNodeExpr of the class' ts.Identifier was used
within each of this situations.
In the ngtsc case, this is proper. In the ngcc case, if the class being
compiled is within an ES5 IIFE, the outer name of the class may have
changed. Thus, the class has both an inner and outer name. The outer name
should continue to be used elsewhere in the compiler and in 'type'.
The 'expression' will live within the IIFE, the `internalType` should be used.
The adjacent statement will also live within the IIFE, the `adjacentType` should be used.
This commit introduces `ReflectionHost.getInternalNameOfClass()` and
`ReflectionHost.getAdjacentNameOfClass()`, which the compiler can use to
query for the correct name to use.
PR Close #33533
2019-11-01 16:55:09 +00:00
|
|
|
const typeForCtor = !isDelegatedMetadata(meta) ?
|
|
|
|
new o.BinaryOperatorExpr(o.BinaryOperator.Or, t, meta.internalType) :
|
|
|
|
t;
|
2018-04-24 11:34:11 -07:00
|
|
|
|
2018-07-16 16:36:31 -07:00
|
|
|
let ctorExpr: o.Expression|null = null;
|
|
|
|
if (meta.deps !== null) {
|
|
|
|
// There is a constructor (either explicitly or implicitly defined).
|
feat(ivy): compile @Injectable on classes not meant for DI (#28523)
In the past, @Injectable had no side effects and existing Angular code is
therefore littered with @Injectable usage on classes which are not intended
to be injected.
A common example is:
@Injectable()
class Foo {
constructor(private notInjectable: string) {}
}
and somewhere else:
providers: [{provide: Foo, useFactory: ...})
Here, there is no need for Foo to be injectable - indeed, it's impossible
for the DI system to create an instance of it, as it has a non-injectable
constructor. The provider configures a factory for the DI system to be
able to create instances of Foo.
Adding @Injectable in Ivy signifies that the class's own constructor, and
not a provider, determines how the class will be created.
This commit adds logic to compile classes which are marked with @Injectable
but are otherwise not injectable, and create an ngInjectableDef field with
a factory function that throws an error. This way, existing code in the wild
continues to compile, but if someone attempts to use the injectable it will
fail with a useful error message.
In the case where strictInjectionParameters is set to true, a compile-time
error is thrown instead of the runtime error, as ngtsc has enough
information to determine when injection couldn't possibly be valid.
PR Close #28523
2019-01-31 14:23:54 -08:00
|
|
|
if (meta.deps !== 'invalid') {
|
2019-10-03 21:54:49 +02:00
|
|
|
ctorExpr = new o.InstantiateExpr(
|
|
|
|
typeForCtor,
|
|
|
|
injectDependencies(meta.deps, meta.injectFn, meta.target === R3FactoryTarget.Pipe));
|
feat(ivy): compile @Injectable on classes not meant for DI (#28523)
In the past, @Injectable had no side effects and existing Angular code is
therefore littered with @Injectable usage on classes which are not intended
to be injected.
A common example is:
@Injectable()
class Foo {
constructor(private notInjectable: string) {}
}
and somewhere else:
providers: [{provide: Foo, useFactory: ...})
Here, there is no need for Foo to be injectable - indeed, it's impossible
for the DI system to create an instance of it, as it has a non-injectable
constructor. The provider configures a factory for the DI system to be
able to create instances of Foo.
Adding @Injectable in Ivy signifies that the class's own constructor, and
not a provider, determines how the class will be created.
This commit adds logic to compile classes which are marked with @Injectable
but are otherwise not injectable, and create an ngInjectableDef field with
a factory function that throws an error. This way, existing code in the wild
continues to compile, but if someone attempts to use the injectable it will
fail with a useful error message.
In the case where strictInjectionParameters is set to true, a compile-time
error is thrown instead of the runtime error, as ngtsc has enough
information to determine when injection couldn't possibly be valid.
PR Close #28523
2019-01-31 14:23:54 -08:00
|
|
|
}
|
2018-07-16 16:36:31 -07:00
|
|
|
} else {
|
|
|
|
const baseFactory = o.variable(`ɵ${meta.name}_BaseFactory`);
|
|
|
|
const getInheritedFactory = o.importExpr(R3.getInheritedFactory);
|
2018-08-10 11:26:41 +01:00
|
|
|
const baseFactoryStmt =
|
refactor(ivy): split `type` into `type`, `internalType` and `adjacentType` (#33533)
When compiling an Angular decorator (e.g. Directive), @angular/compiler
generates an 'expression' to be added as a static definition field
on the class, a 'type' which will be added for that field to the .d.ts
file, and a statement adjacent to the class that calls `setClassMetadata()`.
Previously, the same WrappedNodeExpr of the class' ts.Identifier was used
within each of this situations.
In the ngtsc case, this is proper. In the ngcc case, if the class being
compiled is within an ES5 IIFE, the outer name of the class may have
changed. Thus, the class has both an inner and outer name. The outer name
should continue to be used elsewhere in the compiler and in 'type'.
The 'expression' will live within the IIFE, the `internalType` should be used.
The adjacent statement will also live within the IIFE, the `adjacentType` should be used.
This commit introduces `ReflectionHost.getInternalNameOfClass()` and
`ReflectionHost.getAdjacentNameOfClass()`, which the compiler can use to
query for the correct name to use.
PR Close #33533
2019-11-01 16:55:09 +00:00
|
|
|
baseFactory.set(getInheritedFactory.callFn([meta.internalType]))
|
|
|
|
.toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Exported, o.StmtModifier.Final]);
|
2018-07-16 16:36:31 -07:00
|
|
|
statements.push(baseFactoryStmt);
|
2018-04-24 11:34:11 -07:00
|
|
|
|
2018-07-16 16:36:31 -07:00
|
|
|
// There is no constructor, use the base class' factory to construct typeForCtor.
|
|
|
|
ctorExpr = baseFactory.callFn([typeForCtor]);
|
|
|
|
}
|
|
|
|
const ctorExprFinal = ctorExpr;
|
|
|
|
|
|
|
|
const body: o.Statement[] = [];
|
|
|
|
let retExpr: o.Expression|null = null;
|
|
|
|
|
|
|
|
function makeConditionalFactory(nonCtorExpr: o.Expression): o.ReadVarExpr {
|
|
|
|
const r = o.variable('r');
|
|
|
|
body.push(r.set(o.NULL_EXPR).toDeclStmt());
|
feat(ivy): compile @Injectable on classes not meant for DI (#28523)
In the past, @Injectable had no side effects and existing Angular code is
therefore littered with @Injectable usage on classes which are not intended
to be injected.
A common example is:
@Injectable()
class Foo {
constructor(private notInjectable: string) {}
}
and somewhere else:
providers: [{provide: Foo, useFactory: ...})
Here, there is no need for Foo to be injectable - indeed, it's impossible
for the DI system to create an instance of it, as it has a non-injectable
constructor. The provider configures a factory for the DI system to be
able to create instances of Foo.
Adding @Injectable in Ivy signifies that the class's own constructor, and
not a provider, determines how the class will be created.
This commit adds logic to compile classes which are marked with @Injectable
but are otherwise not injectable, and create an ngInjectableDef field with
a factory function that throws an error. This way, existing code in the wild
continues to compile, but if someone attempts to use the injectable it will
fail with a useful error message.
In the case where strictInjectionParameters is set to true, a compile-time
error is thrown instead of the runtime error, as ngtsc has enough
information to determine when injection couldn't possibly be valid.
PR Close #28523
2019-01-31 14:23:54 -08:00
|
|
|
let ctorStmt: o.Statement|null = null;
|
|
|
|
if (ctorExprFinal !== null) {
|
|
|
|
ctorStmt = r.set(ctorExprFinal).toStmt();
|
|
|
|
} else {
|
2019-10-03 21:54:49 +02:00
|
|
|
ctorStmt = o.importExpr(R3.invalidFactory).callFn([]).toStmt();
|
feat(ivy): compile @Injectable on classes not meant for DI (#28523)
In the past, @Injectable had no side effects and existing Angular code is
therefore littered with @Injectable usage on classes which are not intended
to be injected.
A common example is:
@Injectable()
class Foo {
constructor(private notInjectable: string) {}
}
and somewhere else:
providers: [{provide: Foo, useFactory: ...})
Here, there is no need for Foo to be injectable - indeed, it's impossible
for the DI system to create an instance of it, as it has a non-injectable
constructor. The provider configures a factory for the DI system to be
able to create instances of Foo.
Adding @Injectable in Ivy signifies that the class's own constructor, and
not a provider, determines how the class will be created.
This commit adds logic to compile classes which are marked with @Injectable
but are otherwise not injectable, and create an ngInjectableDef field with
a factory function that throws an error. This way, existing code in the wild
continues to compile, but if someone attempts to use the injectable it will
fail with a useful error message.
In the case where strictInjectionParameters is set to true, a compile-time
error is thrown instead of the runtime error, as ngtsc has enough
information to determine when injection couldn't possibly be valid.
PR Close #28523
2019-01-31 14:23:54 -08:00
|
|
|
}
|
|
|
|
body.push(o.ifStmt(t, [ctorStmt], [r.set(nonCtorExpr).toStmt()]));
|
2018-07-16 16:36:31 -07:00
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isDelegatedMetadata(meta) && meta.delegateType === R3FactoryDelegateType.Factory) {
|
|
|
|
const delegateFactory = o.variable(`ɵ${meta.name}_BaseFactory`);
|
|
|
|
const getFactoryOf = o.importExpr(R3.getFactoryOf);
|
refactor(ivy): split `type` into `type`, `internalType` and `adjacentType` (#33533)
When compiling an Angular decorator (e.g. Directive), @angular/compiler
generates an 'expression' to be added as a static definition field
on the class, a 'type' which will be added for that field to the .d.ts
file, and a statement adjacent to the class that calls `setClassMetadata()`.
Previously, the same WrappedNodeExpr of the class' ts.Identifier was used
within each of this situations.
In the ngtsc case, this is proper. In the ngcc case, if the class being
compiled is within an ES5 IIFE, the outer name of the class may have
changed. Thus, the class has both an inner and outer name. The outer name
should continue to be used elsewhere in the compiler and in 'type'.
The 'expression' will live within the IIFE, the `internalType` should be used.
The adjacent statement will also live within the IIFE, the `adjacentType` should be used.
This commit introduces `ReflectionHost.getInternalNameOfClass()` and
`ReflectionHost.getAdjacentNameOfClass()`, which the compiler can use to
query for the correct name to use.
PR Close #33533
2019-11-01 16:55:09 +00:00
|
|
|
if (meta.delegate.isEquivalent(meta.internalType)) {
|
2018-07-16 16:36:31 -07:00
|
|
|
throw new Error(`Illegal state: compiling factory that delegates to itself`);
|
|
|
|
}
|
2018-08-10 11:26:41 +01:00
|
|
|
const delegateFactoryStmt =
|
|
|
|
delegateFactory.set(getFactoryOf.callFn([meta.delegate])).toDeclStmt(o.INFERRED_TYPE, [
|
|
|
|
o.StmtModifier.Exported, o.StmtModifier.Final
|
|
|
|
]);
|
2018-07-16 16:36:31 -07:00
|
|
|
|
|
|
|
statements.push(delegateFactoryStmt);
|
2018-10-16 10:28:23 -07:00
|
|
|
retExpr = makeConditionalFactory(delegateFactory.callFn([]));
|
2018-07-16 16:36:31 -07:00
|
|
|
} else if (isDelegatedMetadata(meta)) {
|
|
|
|
// This type is created with a delegated factory. If a type parameter is not specified, call
|
|
|
|
// the factory instead.
|
2019-10-03 21:54:49 +02:00
|
|
|
const delegateArgs =
|
|
|
|
injectDependencies(meta.delegateDeps, meta.injectFn, meta.target === R3FactoryTarget.Pipe);
|
|
|
|
// Either call `new delegate(...)` or `delegate(...)` depending on meta.delegateType.
|
2018-07-16 16:36:31 -07:00
|
|
|
const factoryExpr = new (
|
|
|
|
meta.delegateType === R3FactoryDelegateType.Class ?
|
|
|
|
o.InstantiateExpr :
|
|
|
|
o.InvokeFunctionExpr)(meta.delegate, delegateArgs);
|
|
|
|
retExpr = makeConditionalFactory(factoryExpr);
|
|
|
|
} else if (isExpressionFactoryMetadata(meta)) {
|
|
|
|
// TODO(alxhub): decide whether to lower the value here or in the caller
|
|
|
|
retExpr = makeConditionalFactory(meta.expression);
|
|
|
|
} else {
|
|
|
|
retExpr = ctorExpr;
|
|
|
|
}
|
|
|
|
|
feat(ivy): compile @Injectable on classes not meant for DI (#28523)
In the past, @Injectable had no side effects and existing Angular code is
therefore littered with @Injectable usage on classes which are not intended
to be injected.
A common example is:
@Injectable()
class Foo {
constructor(private notInjectable: string) {}
}
and somewhere else:
providers: [{provide: Foo, useFactory: ...})
Here, there is no need for Foo to be injectable - indeed, it's impossible
for the DI system to create an instance of it, as it has a non-injectable
constructor. The provider configures a factory for the DI system to be
able to create instances of Foo.
Adding @Injectable in Ivy signifies that the class's own constructor, and
not a provider, determines how the class will be created.
This commit adds logic to compile classes which are marked with @Injectable
but are otherwise not injectable, and create an ngInjectableDef field with
a factory function that throws an error. This way, existing code in the wild
continues to compile, but if someone attempts to use the injectable it will
fail with a useful error message.
In the case where strictInjectionParameters is set to true, a compile-time
error is thrown instead of the runtime error, as ngtsc has enough
information to determine when injection couldn't possibly be valid.
PR Close #28523
2019-01-31 14:23:54 -08:00
|
|
|
if (retExpr !== null) {
|
|
|
|
body.push(new o.ReturnStatement(retExpr));
|
|
|
|
} else {
|
2019-10-03 21:54:49 +02:00
|
|
|
body.push(o.importExpr(R3.invalidFactory).callFn([]).toStmt());
|
feat(ivy): compile @Injectable on classes not meant for DI (#28523)
In the past, @Injectable had no side effects and existing Angular code is
therefore littered with @Injectable usage on classes which are not intended
to be injected.
A common example is:
@Injectable()
class Foo {
constructor(private notInjectable: string) {}
}
and somewhere else:
providers: [{provide: Foo, useFactory: ...})
Here, there is no need for Foo to be injectable - indeed, it's impossible
for the DI system to create an instance of it, as it has a non-injectable
constructor. The provider configures a factory for the DI system to be
able to create instances of Foo.
Adding @Injectable in Ivy signifies that the class's own constructor, and
not a provider, determines how the class will be created.
This commit adds logic to compile classes which are marked with @Injectable
but are otherwise not injectable, and create an ngInjectableDef field with
a factory function that throws an error. This way, existing code in the wild
continues to compile, but if someone attempts to use the injectable it will
fail with a useful error message.
In the case where strictInjectionParameters is set to true, a compile-time
error is thrown instead of the runtime error, as ngtsc has enough
information to determine when injection couldn't possibly be valid.
PR Close #28523
2019-01-31 14:23:54 -08:00
|
|
|
}
|
|
|
|
|
2018-07-16 16:36:31 -07:00
|
|
|
return {
|
|
|
|
factory: o.fn(
|
feat(ivy): compile @Injectable on classes not meant for DI (#28523)
In the past, @Injectable had no side effects and existing Angular code is
therefore littered with @Injectable usage on classes which are not intended
to be injected.
A common example is:
@Injectable()
class Foo {
constructor(private notInjectable: string) {}
}
and somewhere else:
providers: [{provide: Foo, useFactory: ...})
Here, there is no need for Foo to be injectable - indeed, it's impossible
for the DI system to create an instance of it, as it has a non-injectable
constructor. The provider configures a factory for the DI system to be
able to create instances of Foo.
Adding @Injectable in Ivy signifies that the class's own constructor, and
not a provider, determines how the class will be created.
This commit adds logic to compile classes which are marked with @Injectable
but are otherwise not injectable, and create an ngInjectableDef field with
a factory function that throws an error. This way, existing code in the wild
continues to compile, but if someone attempts to use the injectable it will
fail with a useful error message.
In the case where strictInjectionParameters is set to true, a compile-time
error is thrown instead of the runtime error, as ngtsc has enough
information to determine when injection couldn't possibly be valid.
PR Close #28523
2019-01-31 14:23:54 -08:00
|
|
|
[new o.FnParam('t', o.DYNAMIC_TYPE)], body, o.INFERRED_TYPE, undefined,
|
|
|
|
`${meta.name}_Factory`),
|
2018-07-16 16:36:31 -07:00
|
|
|
statements,
|
2019-08-12 09:26:20 +03:00
|
|
|
type: o.expressionType(
|
|
|
|
o.importExpr(R3.FactoryDef, [typeWithParameters(meta.type, meta.typeArgumentCount)]))
|
2018-07-16 16:36:31 -07:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function injectDependencies(
|
2019-07-12 20:15:12 +02:00
|
|
|
deps: R3DependencyMetadata[], injectFn: o.ExternalReference, isPipe: boolean): o.Expression[] {
|
|
|
|
return deps.map(dep => compileInjectDependency(dep, injectFn, isPipe));
|
2018-04-24 11:34:11 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
function compileInjectDependency(
|
2019-07-12 20:15:12 +02:00
|
|
|
dep: R3DependencyMetadata, injectFn: o.ExternalReference, isPipe: boolean): o.Expression {
|
2018-04-24 11:34:11 -07:00
|
|
|
// Interpret the dependency according to its resolved type.
|
|
|
|
switch (dep.resolved) {
|
2019-07-12 20:15:12 +02:00
|
|
|
case R3ResolvedDependencyType.Token:
|
|
|
|
case R3ResolvedDependencyType.ChangeDetectorRef:
|
2018-04-24 11:34:11 -07:00
|
|
|
// Build up the injection flags according to the metadata.
|
|
|
|
const flags = InjectFlags.Default | (dep.self ? InjectFlags.Self : 0) |
|
|
|
|
(dep.skipSelf ? InjectFlags.SkipSelf : 0) | (dep.host ? InjectFlags.Host : 0) |
|
|
|
|
(dep.optional ? InjectFlags.Optional : 0);
|
|
|
|
|
|
|
|
// If this dependency is optional or otherwise has non-default flags, then additional
|
|
|
|
// parameters describing how to inject the dependency must be passed to the inject function
|
|
|
|
// that's being used.
|
2019-07-12 20:15:12 +02:00
|
|
|
let flagsParam: o.LiteralExpr|null =
|
|
|
|
(flags !== InjectFlags.Default || dep.optional) ? o.literal(flags) : null;
|
|
|
|
|
|
|
|
// We have a separate instruction for injecting ChangeDetectorRef into a pipe.
|
|
|
|
if (isPipe && dep.resolved === R3ResolvedDependencyType.ChangeDetectorRef) {
|
|
|
|
return o.importExpr(R3.injectPipeChangeDetectorRef).callFn(flagsParam ? [flagsParam] : []);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Build up the arguments to the injectFn call.
|
|
|
|
const injectArgs = [dep.token];
|
|
|
|
if (flagsParam) {
|
|
|
|
injectArgs.push(flagsParam);
|
2018-04-24 11:34:11 -07:00
|
|
|
}
|
|
|
|
return o.importExpr(injectFn).callFn(injectArgs);
|
|
|
|
case R3ResolvedDependencyType.Attribute:
|
|
|
|
// In the case of attributes, the attribute name in question is given as the token.
|
|
|
|
return o.importExpr(R3.injectAttribute).callFn([dep.token]);
|
|
|
|
default:
|
|
|
|
return unsupported(
|
|
|
|
`Unknown R3ResolvedDependencyType: ${R3ResolvedDependencyType[dep.resolved]}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A helper function useful for extracting `R3DependencyMetadata` from a Render2
|
|
|
|
* `CompileTypeMetadata` instance.
|
|
|
|
*/
|
|
|
|
export function dependenciesFromGlobalMetadata(
|
|
|
|
type: CompileTypeMetadata, outputCtx: OutputContext,
|
|
|
|
reflector: CompileReflector): R3DependencyMetadata[] {
|
|
|
|
// Use the `CompileReflector` to look up references to some well-known Angular types. These will
|
|
|
|
// be compared with the token to statically determine whether the token has significance to
|
|
|
|
// Angular, and set the correct `R3ResolvedDependencyType` as a result.
|
|
|
|
const injectorRef = reflector.resolveExternalReference(Identifiers.Injector);
|
|
|
|
|
|
|
|
// Iterate through the type's DI dependencies and produce `R3DependencyMetadata` for each of them.
|
|
|
|
const deps: R3DependencyMetadata[] = [];
|
|
|
|
for (let dependency of type.diDeps) {
|
|
|
|
if (dependency.token) {
|
|
|
|
const tokenRef = tokenReference(dependency.token);
|
2018-10-23 14:28:15 -07:00
|
|
|
let resolved: R3ResolvedDependencyType = dependency.isAttribute ?
|
|
|
|
R3ResolvedDependencyType.Attribute :
|
|
|
|
R3ResolvedDependencyType.Token;
|
2018-04-24 11:34:11 -07:00
|
|
|
|
|
|
|
// In the case of most dependencies, the token will be a reference to a type. Sometimes,
|
|
|
|
// however, it can be a string, in the case of older Angular code or @Attribute injection.
|
|
|
|
const token =
|
|
|
|
tokenRef instanceof StaticSymbol ? outputCtx.importExpr(tokenRef) : o.literal(tokenRef);
|
|
|
|
|
|
|
|
// Construct the dependency.
|
|
|
|
deps.push({
|
|
|
|
token,
|
|
|
|
resolved,
|
|
|
|
host: !!dependency.isHost,
|
|
|
|
optional: !!dependency.isOptional,
|
|
|
|
self: !!dependency.isSelf,
|
|
|
|
skipSelf: !!dependency.isSkipSelf,
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
unsupported('dependency without a token');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return deps;
|
|
|
|
}
|
2018-07-16 16:36:31 -07:00
|
|
|
|
|
|
|
function isDelegatedMetadata(meta: R3FactoryMetadata): meta is R3DelegatedFactoryMetadata|
|
|
|
|
R3DelegatedFnOrClassMetadata {
|
|
|
|
return (meta as any).delegateType !== undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
function isExpressionFactoryMetadata(meta: R3FactoryMetadata): meta is R3ExpressionFactoryMetadata {
|
|
|
|
return (meta as any).expression !== undefined;
|
2018-09-21 18:38:13 -07:00
|
|
|
}
|