2018-04-06 09:53:10 -07:00
|
|
|
/**
|
|
|
|
* @license
|
2020-05-19 12:08:49 -07:00
|
|
|
* Copyright Google LLC All Rights Reserved.
|
2018-04-06 09:53:10 -07:00
|
|
|
*
|
|
|
|
* 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 * as o from './output/output_ast';
|
2021-03-21 17:31:20 +00:00
|
|
|
import {generateForwardRef} from './render3/partial/util';
|
2021-03-25 21:25:03 +00:00
|
|
|
import {compileFactoryFunction, FactoryTarget, R3DependencyMetadata, R3FactoryDelegateType, R3FactoryMetadata} from './render3/r3_factory';
|
2021-03-21 17:31:20 +00:00
|
|
|
import {Identifiers} from './render3/r3_identifiers';
|
|
|
|
import {R3CompiledExpression, R3Reference, typeWithParameters} from './render3/util';
|
2021-03-10 13:28:04 +00:00
|
|
|
import {DefinitionMap} from './render3/view/util';
|
2018-04-06 09:53:10 -07:00
|
|
|
|
2018-05-09 08:35:25 -07:00
|
|
|
export interface R3InjectableMetadata {
|
2018-04-06 09:53:10 -07:00
|
|
|
name: string;
|
2019-12-18 14:03:05 +00:00
|
|
|
type: R3Reference;
|
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
|
|
|
internalType: o.Expression;
|
2018-11-10 02:58:33 +01:00
|
|
|
typeArgumentCount: number;
|
2021-03-21 17:31:20 +00:00
|
|
|
providedIn: R3ProviderExpression;
|
|
|
|
useClass?: R3ProviderExpression;
|
2018-05-09 08:35:25 -07:00
|
|
|
useFactory?: o.Expression;
|
2021-03-21 17:31:20 +00:00
|
|
|
useExisting?: R3ProviderExpression;
|
|
|
|
useValue?: R3ProviderExpression;
|
|
|
|
deps?: R3DependencyMetadata[];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* An expression used when instantiating an injectable.
|
|
|
|
*
|
|
|
|
* This is the type of the `useClass`, `useExisting` and `useValue` properties of
|
|
|
|
* `R3InjectableMetadata` since those can refer to types that may eagerly reference types that have
|
|
|
|
* not yet been defined.
|
|
|
|
*/
|
|
|
|
export interface R3ProviderExpression<T extends o.Expression = o.Expression> {
|
|
|
|
/**
|
|
|
|
* The expression that is used to instantiate the Injectable.
|
|
|
|
*/
|
|
|
|
expression: T;
|
|
|
|
/**
|
|
|
|
* If true, then the `expression` contains a reference to something that has not yet been
|
|
|
|
* defined.
|
|
|
|
*
|
|
|
|
* This means that the expression must not be eagerly evaluated. Instead it must be wrapped in a
|
|
|
|
* function closure that will be evaluated lazily to allow the definition of the expression to be
|
|
|
|
* evaluated first.
|
|
|
|
*
|
|
|
|
* In some cases the expression will naturally be placed inside such a function closure, such as
|
|
|
|
* in a fully compiled factory function. In those case nothing more needs to be done.
|
|
|
|
*
|
|
|
|
* But in other cases, such as partial-compilation the expression will be located in top level
|
|
|
|
* code so will need to be wrapped in a function that is passed to a `forwardRef()` call.
|
|
|
|
*/
|
|
|
|
isForwardRef: boolean;
|
2018-04-06 09:53:10 -07:00
|
|
|
}
|
|
|
|
|
2021-03-21 17:31:20 +00:00
|
|
|
export function createR3ProviderExpression<T extends o.Expression>(
|
|
|
|
expression: T, isForwardRef: boolean): R3ProviderExpression<T> {
|
|
|
|
return {expression, isForwardRef};
|
|
|
|
}
|
|
|
|
|
|
|
|
export function compileInjectable(
|
|
|
|
meta: R3InjectableMetadata, resolveForwardRefs: boolean): R3CompiledExpression {
|
2021-03-10 15:24:11 +00:00
|
|
|
let result: {expression: o.Expression, statements: o.Statement[]}|null = null;
|
2018-05-09 08:35:25 -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
|
|
|
const factoryMeta: R3FactoryMetadata = {
|
2018-07-16 16:36:31 -07:00
|
|
|
name: meta.name,
|
|
|
|
type: meta.type,
|
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
|
|
|
internalType: meta.internalType,
|
2019-08-12 09:26:20 +03:00
|
|
|
typeArgumentCount: meta.typeArgumentCount,
|
2019-09-01 12:26:04 +02:00
|
|
|
deps: [],
|
2021-03-25 21:25:03 +00:00
|
|
|
target: FactoryTarget.Injectable,
|
2018-07-16 16:36:31 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
if (meta.useClass !== undefined) {
|
|
|
|
// meta.useClass has two modes of operation. Either deps are specified, in which case `new` is
|
|
|
|
// used to instantiate the class with dependencies injected, or deps are not specified and
|
|
|
|
// the factory of the class is used to instantiate it.
|
|
|
|
//
|
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
|
|
|
// A special case exists for useClass: Type where Type is the injectable type itself and no
|
|
|
|
// deps are specified, in which case 'useClass' is effectively ignored.
|
2018-05-09 08:35:25 -07:00
|
|
|
|
2021-03-21 17:31:20 +00:00
|
|
|
const useClassOnSelf = meta.useClass.expression.isEquivalent(meta.internalType);
|
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 deps: R3DependencyMetadata[]|undefined = undefined;
|
2021-03-21 17:31:20 +00:00
|
|
|
if (meta.deps !== undefined) {
|
|
|
|
deps = meta.deps;
|
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-05-09 08:35:25 -07:00
|
|
|
|
2018-07-16 16:36:31 -07:00
|
|
|
if (deps !== undefined) {
|
|
|
|
// factory: () => new meta.useClass(...deps)
|
|
|
|
result = compileFactoryFunction({
|
|
|
|
...factoryMeta,
|
2021-03-21 17:31:20 +00:00
|
|
|
delegate: meta.useClass.expression,
|
2018-07-16 16:36:31 -07:00
|
|
|
delegateDeps: deps,
|
|
|
|
delegateType: R3FactoryDelegateType.Class,
|
2018-05-09 08:35:25 -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
|
|
|
} else if (useClassOnSelf) {
|
|
|
|
result = compileFactoryFunction(factoryMeta);
|
2018-05-09 08:35:25 -07:00
|
|
|
} else {
|
2021-03-21 17:31:20 +00:00
|
|
|
result = {
|
|
|
|
statements: [],
|
|
|
|
expression: delegateToFactory(
|
|
|
|
meta.type.value as o.WrappedNodeExpr<any>,
|
|
|
|
meta.useClass.expression as o.WrappedNodeExpr<any>, resolveForwardRefs)
|
|
|
|
};
|
2019-09-01 12:26:04 +02:00
|
|
|
}
|
|
|
|
} else if (meta.useFactory !== undefined) {
|
2021-03-21 17:31:20 +00:00
|
|
|
if (meta.deps !== undefined) {
|
2018-07-16 16:36:31 -07:00
|
|
|
result = compileFactoryFunction({
|
|
|
|
...factoryMeta,
|
2019-09-01 12:26:04 +02:00
|
|
|
delegate: meta.useFactory,
|
2021-03-21 17:31:20 +00:00
|
|
|
delegateDeps: meta.deps || [],
|
2019-09-01 12:26:04 +02:00
|
|
|
delegateType: R3FactoryDelegateType.Function,
|
2018-07-16 16:36:31 -07:00
|
|
|
});
|
2019-09-01 12:26:04 +02:00
|
|
|
} else {
|
|
|
|
result = {
|
|
|
|
statements: [],
|
2021-03-10 15:24:11 +00:00
|
|
|
expression: o.fn([], [new o.ReturnStatement(meta.useFactory.callFn([]))])
|
2019-09-01 12:26:04 +02:00
|
|
|
};
|
2018-05-09 08:35:25 -07:00
|
|
|
}
|
2018-04-06 09:53:10 -07:00
|
|
|
} else if (meta.useValue !== undefined) {
|
2018-05-09 08:35:25 -07:00
|
|
|
// Note: it's safe to use `meta.useValue` instead of the `USE_VALUE in meta` check used for
|
|
|
|
// client code because meta.useValue is an Expression which will be defined even if the actual
|
|
|
|
// value is undefined.
|
2018-07-16 16:36:31 -07:00
|
|
|
result = compileFactoryFunction({
|
|
|
|
...factoryMeta,
|
2021-03-21 17:31:20 +00:00
|
|
|
expression: meta.useValue.expression,
|
2018-07-16 16:36:31 -07:00
|
|
|
});
|
2018-04-06 09:53:10 -07:00
|
|
|
} else if (meta.useExisting !== undefined) {
|
2018-05-09 08:35:25 -07:00
|
|
|
// useExisting is an `inject` call on the existing token.
|
2018-07-16 16:36:31 -07:00
|
|
|
result = compileFactoryFunction({
|
|
|
|
...factoryMeta,
|
2021-03-21 17:31:20 +00:00
|
|
|
expression: o.importExpr(Identifiers.inject).callFn([meta.useExisting.expression]),
|
2018-05-09 08:35:25 -07:00
|
|
|
});
|
2018-07-16 16:36:31 -07:00
|
|
|
} else {
|
2021-03-21 17:31:20 +00:00
|
|
|
result = {
|
|
|
|
statements: [],
|
|
|
|
expression: delegateToFactory(
|
|
|
|
meta.type.value as o.WrappedNodeExpr<any>, meta.internalType as o.WrappedNodeExpr<any>,
|
|
|
|
resolveForwardRefs)
|
|
|
|
};
|
2018-04-06 09:53:10 -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
|
|
|
const token = meta.internalType;
|
2018-04-06 09:53:10 -07:00
|
|
|
|
2021-03-10 13:28:04 +00:00
|
|
|
const injectableProps =
|
|
|
|
new DefinitionMap<{token: o.Expression, factory: o.Expression, providedIn: o.Expression}>();
|
|
|
|
injectableProps.set('token', token);
|
2021-03-10 15:24:11 +00:00
|
|
|
injectableProps.set('factory', result.expression);
|
2019-11-27 16:25:47 -08:00
|
|
|
|
|
|
|
// Only generate providedIn property if it has a non-null value
|
2021-03-21 17:31:20 +00:00
|
|
|
if ((meta.providedIn.expression as o.LiteralExpr).value !== null) {
|
|
|
|
injectableProps.set(
|
|
|
|
'providedIn',
|
|
|
|
meta.providedIn.isForwardRef ? generateForwardRef(meta.providedIn.expression) :
|
|
|
|
meta.providedIn.expression);
|
2019-11-27 16:25:47 -08:00
|
|
|
}
|
|
|
|
|
2021-03-05 16:25:04 -08:00
|
|
|
const expression = o.importExpr(Identifiers.ɵɵdefineInjectable)
|
2021-03-10 13:28:04 +00:00
|
|
|
.callFn([injectableProps.toLiteralMap()], undefined, true);
|
2018-04-06 09:53:10 -07:00
|
|
|
return {
|
2018-07-16 16:36:31 -07:00
|
|
|
expression,
|
2021-03-21 17:31:20 +00:00
|
|
|
type: createInjectableType(meta),
|
2018-07-16 16:36:31 -07:00
|
|
|
statements: result.statements,
|
2018-04-06 09:53:10 -07:00
|
|
|
};
|
|
|
|
}
|
2019-09-01 12:26:04 +02:00
|
|
|
|
2021-03-21 17:31:20 +00:00
|
|
|
export function createInjectableType(meta: R3InjectableMetadata) {
|
|
|
|
return new o.ExpressionType(o.importExpr(
|
|
|
|
Identifiers.InjectableDeclaration,
|
|
|
|
[typeWithParameters(meta.type.type, meta.typeArgumentCount)]));
|
|
|
|
}
|
|
|
|
|
|
|
|
function delegateToFactory(
|
|
|
|
type: o.WrappedNodeExpr<any>, internalType: o.WrappedNodeExpr<any>,
|
|
|
|
unwrapForwardRefs: boolean): o.Expression {
|
|
|
|
if (type.node === internalType.node) {
|
|
|
|
// The types are the same, so we can simply delegate directly to the type's factory.
|
|
|
|
// ```
|
|
|
|
// factory: type.ɵfac
|
|
|
|
// ```
|
|
|
|
return internalType.prop('ɵfac');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!unwrapForwardRefs) {
|
|
|
|
// The type is not wrapped in a `forwardRef()`, so we create a simple factory function that
|
|
|
|
// accepts a sub-type as an argument.
|
|
|
|
// ```
|
|
|
|
// factory: function(t) { return internalType.ɵfac(t); }
|
|
|
|
// ```
|
|
|
|
return createFactoryFunction(internalType);
|
|
|
|
}
|
|
|
|
|
|
|
|
// The internalType is actually wrapped in a `forwardRef()` so we need to resolve that before
|
|
|
|
// calling its factory.
|
|
|
|
// ```
|
|
|
|
// factory: function(t) { return core.resolveForwardRef(type).ɵfac(t); }
|
|
|
|
// ```
|
|
|
|
const unwrappedType = o.importExpr(Identifiers.resolveForwardRef).callFn([internalType]);
|
|
|
|
return createFactoryFunction(unwrappedType);
|
|
|
|
}
|
|
|
|
|
|
|
|
function createFactoryFunction(type: o.Expression): o.FunctionExpr {
|
|
|
|
return o.fn(
|
|
|
|
[new o.FnParam('t', o.DYNAMIC_TYPE)],
|
|
|
|
[new o.ReturnStatement(type.callMethod('ɵfac', [o.variable('t')]))]);
|
2019-09-01 12:26:04 +02:00
|
|
|
}
|