refactor(ivy): generate ngFactoryDef for injectables (#32433)
With #31953 we moved the factories for components, directives and pipes into a new field called `ngFactoryDef`, however I decided not to do it for injectables, because they needed some extra logic. These changes set up the `ngFactoryDef` for injectables as well. For reference, the extra logic mentioned above is that for injectables we have two code paths: 1. For injectables that don't configure how they should be instantiated, we create a `factory` that proxies to `ngFactoryDef`: ``` // Source @Injectable() class Service {} // Output class Service { static ngInjectableDef = defineInjectable({ factory: () => Service.ngFactoryFn(), }); static ngFactoryFn: (t) => new (t || Service)(); } ``` 2. For injectables that do configure how they're created, we keep the `ngFactoryDef` and generate the factory based on the metadata: ``` // Source @Injectable({ useValue: DEFAULT_IMPL, }) class Service {} // Output export class Service { static ngInjectableDef = defineInjectable({ factory: () => DEFAULT_IMPL, }); static ngFactoryFn: (t) => new (t || Service)(); } ``` PR Close #32433
This commit is contained in:
parent
2729747225
commit
4e35e348af
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ChangeDetectorRef, EventEmitter, Injectable, OnDestroy, Pipe, PipeTransform, WrappedValue, ɵisObservable, ɵisPromise, ɵlooseIdentical} from '@angular/core';
|
import {ChangeDetectorRef, EventEmitter, OnDestroy, Pipe, PipeTransform, WrappedValue, ɵisObservable, ɵisPromise, ɵlooseIdentical} from '@angular/core';
|
||||||
import {Observable, SubscriptionLike} from 'rxjs';
|
import {Observable, SubscriptionLike} from 'rxjs';
|
||||||
import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
|
import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||||
|
|
||||||
|
@ -67,7 +67,6 @@ const _observableStrategy = new ObservableStrategy();
|
||||||
*
|
*
|
||||||
* @publicApi
|
* @publicApi
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
|
||||||
@Pipe({name: 'async', pure: false})
|
@Pipe({name: 'async', pure: false})
|
||||||
export class AsyncPipe implements OnDestroy, PipeTransform {
|
export class AsyncPipe implements OnDestroy, PipeTransform {
|
||||||
private _latestValue: any = null;
|
private _latestValue: any = null;
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Injectable, Pipe, PipeTransform} from '@angular/core';
|
import {Pipe, PipeTransform} from '@angular/core';
|
||||||
import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
|
import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -24,7 +24,6 @@ import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||||
* @ngModule CommonModule
|
* @ngModule CommonModule
|
||||||
* @publicApi
|
* @publicApi
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
|
||||||
@Pipe({name: 'lowercase'})
|
@Pipe({name: 'lowercase'})
|
||||||
export class LowerCasePipe implements PipeTransform {
|
export class LowerCasePipe implements PipeTransform {
|
||||||
/**
|
/**
|
||||||
|
@ -68,7 +67,6 @@ const unicodeWordMatch =
|
||||||
* @ngModule CommonModule
|
* @ngModule CommonModule
|
||||||
* @publicApi
|
* @publicApi
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
|
||||||
@Pipe({name: 'titlecase'})
|
@Pipe({name: 'titlecase'})
|
||||||
export class TitleCasePipe implements PipeTransform {
|
export class TitleCasePipe implements PipeTransform {
|
||||||
/**
|
/**
|
||||||
|
@ -93,7 +91,6 @@ export class TitleCasePipe implements PipeTransform {
|
||||||
* @ngModule CommonModule
|
* @ngModule CommonModule
|
||||||
* @publicApi
|
* @publicApi
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
|
||||||
@Pipe({name: 'uppercase'})
|
@Pipe({name: 'uppercase'})
|
||||||
export class UpperCasePipe implements PipeTransform {
|
export class UpperCasePipe implements PipeTransform {
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Inject, Injectable, LOCALE_ID, Pipe, PipeTransform} from '@angular/core';
|
import {Inject, LOCALE_ID, Pipe, PipeTransform} from '@angular/core';
|
||||||
import {formatDate} from '../i18n/format_date';
|
import {formatDate} from '../i18n/format_date';
|
||||||
import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
|
import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||||
|
|
||||||
|
@ -150,7 +150,6 @@ import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||||
* @publicApi
|
* @publicApi
|
||||||
*/
|
*/
|
||||||
// clang-format on
|
// clang-format on
|
||||||
@Injectable()
|
|
||||||
@Pipe({name: 'date', pure: true})
|
@Pipe({name: 'date', pure: true})
|
||||||
export class DatePipe implements PipeTransform {
|
export class DatePipe implements PipeTransform {
|
||||||
constructor(@Inject(LOCALE_ID) private locale: string) {}
|
constructor(@Inject(LOCALE_ID) private locale: string) {}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Injectable, LOCALE_ID, Pipe, PipeTransform} from '@angular/core';
|
import {Pipe, PipeTransform} from '@angular/core';
|
||||||
import {NgLocalization, getPluralCategory} from '../i18n/localization';
|
import {NgLocalization, getPluralCategory} from '../i18n/localization';
|
||||||
import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
|
import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||||
|
|
||||||
|
@ -26,7 +26,6 @@ const _INTERPOLATION_REGEXP: RegExp = /#/g;
|
||||||
*
|
*
|
||||||
* @publicApi
|
* @publicApi
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
|
||||||
@Pipe({name: 'i18nPlural', pure: true})
|
@Pipe({name: 'i18nPlural', pure: true})
|
||||||
export class I18nPluralPipe implements PipeTransform {
|
export class I18nPluralPipe implements PipeTransform {
|
||||||
constructor(private _localization: NgLocalization) {}
|
constructor(private _localization: NgLocalization) {}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Injectable, Pipe, PipeTransform} from '@angular/core';
|
import {Pipe, PipeTransform} from '@angular/core';
|
||||||
import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
|
import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -26,7 +26,6 @@ import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||||
*
|
*
|
||||||
* @publicApi
|
* @publicApi
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
|
||||||
@Pipe({name: 'i18nSelect', pure: true})
|
@Pipe({name: 'i18nSelect', pure: true})
|
||||||
export class I18nSelectPipe implements PipeTransform {
|
export class I18nSelectPipe implements PipeTransform {
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Injectable, Pipe, PipeTransform} from '@angular/core';
|
import {Pipe, PipeTransform} from '@angular/core';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ngModule CommonModule
|
* @ngModule CommonModule
|
||||||
|
@ -23,7 +23,6 @@ import {Injectable, Pipe, PipeTransform} from '@angular/core';
|
||||||
*
|
*
|
||||||
* @publicApi
|
* @publicApi
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
|
||||||
@Pipe({name: 'json', pure: false})
|
@Pipe({name: 'json', pure: false})
|
||||||
export class JsonPipe implements PipeTransform {
|
export class JsonPipe implements PipeTransform {
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Injectable, KeyValueChangeRecord, KeyValueChanges, KeyValueDiffer, KeyValueDiffers, Pipe, PipeTransform} from '@angular/core';
|
import {KeyValueChangeRecord, KeyValueChanges, KeyValueDiffer, KeyValueDiffers, Pipe, PipeTransform} from '@angular/core';
|
||||||
|
|
||||||
function makeKeyValuePair<K, V>(key: K, value: V): KeyValue<K, V> {
|
function makeKeyValuePair<K, V>(key: K, value: V): KeyValue<K, V> {
|
||||||
return {key: key, value: value};
|
return {key: key, value: value};
|
||||||
|
@ -43,7 +43,6 @@ export interface KeyValue<K, V> {
|
||||||
*
|
*
|
||||||
* @publicApi
|
* @publicApi
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
|
||||||
@Pipe({name: 'keyvalue', pure: false})
|
@Pipe({name: 'keyvalue', pure: false})
|
||||||
export class KeyValuePipe implements PipeTransform {
|
export class KeyValuePipe implements PipeTransform {
|
||||||
constructor(private readonly differs: KeyValueDiffers) {}
|
constructor(private readonly differs: KeyValueDiffers) {}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Inject, Injectable, LOCALE_ID, Pipe, PipeTransform} from '@angular/core';
|
import {Inject, LOCALE_ID, Pipe, PipeTransform} from '@angular/core';
|
||||||
import {formatCurrency, formatNumber, formatPercent} from '../i18n/format_number';
|
import {formatCurrency, formatNumber, formatPercent} from '../i18n/format_number';
|
||||||
import {getCurrencySymbol} from '../i18n/locale_data_api';
|
import {getCurrencySymbol} from '../i18n/locale_data_api';
|
||||||
import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
|
import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||||
|
@ -46,7 +46,6 @@ import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||||
*
|
*
|
||||||
* @publicApi
|
* @publicApi
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
|
||||||
@Pipe({name: 'number'})
|
@Pipe({name: 'number'})
|
||||||
export class DecimalPipe implements PipeTransform {
|
export class DecimalPipe implements PipeTransform {
|
||||||
constructor(@Inject(LOCALE_ID) private _locale: string) {}
|
constructor(@Inject(LOCALE_ID) private _locale: string) {}
|
||||||
|
@ -100,7 +99,6 @@ export class DecimalPipe implements PipeTransform {
|
||||||
*
|
*
|
||||||
* @publicApi
|
* @publicApi
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
|
||||||
@Pipe({name: 'percent'})
|
@Pipe({name: 'percent'})
|
||||||
export class PercentPipe implements PipeTransform {
|
export class PercentPipe implements PipeTransform {
|
||||||
constructor(@Inject(LOCALE_ID) private _locale: string) {}
|
constructor(@Inject(LOCALE_ID) private _locale: string) {}
|
||||||
|
@ -155,7 +153,6 @@ export class PercentPipe implements PipeTransform {
|
||||||
*
|
*
|
||||||
* @publicApi
|
* @publicApi
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
|
||||||
@Pipe({name: 'currency'})
|
@Pipe({name: 'currency'})
|
||||||
export class CurrencyPipe implements PipeTransform {
|
export class CurrencyPipe implements PipeTransform {
|
||||||
constructor(@Inject(LOCALE_ID) private _locale: string) {}
|
constructor(@Inject(LOCALE_ID) private _locale: string) {}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Injectable, Pipe, PipeTransform} from '@angular/core';
|
import {Pipe, PipeTransform} from '@angular/core';
|
||||||
import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
|
import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -44,7 +44,6 @@ import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||||
*
|
*
|
||||||
* @publicApi
|
* @publicApi
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
|
||||||
@Pipe({name: 'slice', pure: false})
|
@Pipe({name: 'slice', pure: false})
|
||||||
export class SlicePipe implements PipeTransform {
|
export class SlicePipe implements PipeTransform {
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -171,8 +171,12 @@ export class DecorationAnalyzer {
|
||||||
for (const {handler, analysis} of clazz.matches) {
|
for (const {handler, analysis} of clazz.matches) {
|
||||||
const result = handler.compile(clazz.declaration, analysis, constantPool);
|
const result = handler.compile(clazz.declaration, analysis, constantPool);
|
||||||
if (Array.isArray(result)) {
|
if (Array.isArray(result)) {
|
||||||
compilations.push(...result);
|
result.forEach(current => {
|
||||||
} else {
|
if (!compilations.some(compilation => compilation.name === current.name)) {
|
||||||
|
compilations.push(current);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (!compilations.some(compilation => compilation.name === result.name)) {
|
||||||
compilations.push(result);
|
compilations.push(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ConstantPool, CssSelector, DEFAULT_INTERPOLATION_CONFIG, DomElementSchemaRegistry, Expression, ExternalExpr, InterpolationConfig, LexerRange, ParseError, ParseSourceFile, ParseTemplateOptions, R3ComponentMetadata, R3TargetBinder, SchemaMetadata, SelectorMatcher, Statement, TmplAstNode, WrappedNodeExpr, compileComponentFromMetadata, makeBindingParser, parseTemplate} from '@angular/compiler';
|
import {ConstantPool, CssSelector, DEFAULT_INTERPOLATION_CONFIG, DomElementSchemaRegistry, Expression, ExternalExpr, Identifiers, InterpolationConfig, LexerRange, ParseError, ParseSourceFile, ParseTemplateOptions, R3ComponentMetadata, R3TargetBinder, SchemaMetadata, SelectorMatcher, Statement, TmplAstNode, WrappedNodeExpr, compileComponentFromMetadata, makeBindingParser, parseTemplate} from '@angular/compiler';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {CycleAnalyzer} from '../../cycles';
|
import {CycleAnalyzer} from '../../cycles';
|
||||||
|
@ -486,7 +486,7 @@ export class ComponentDecoratorHandler implements
|
||||||
CompileResult[] {
|
CompileResult[] {
|
||||||
const meta = analysis.meta;
|
const meta = analysis.meta;
|
||||||
const res = compileComponentFromMetadata(meta, pool, makeBindingParser());
|
const res = compileComponentFromMetadata(meta, pool, makeBindingParser());
|
||||||
const factoryRes = compileNgFactoryDefField(meta);
|
const factoryRes = compileNgFactoryDefField({...meta, injectFn: Identifiers.directiveInject});
|
||||||
if (analysis.metadataStmt !== null) {
|
if (analysis.metadataStmt !== null) {
|
||||||
factoryRes.statements.push(analysis.metadataStmt);
|
factoryRes.statements.push(analysis.metadataStmt);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ConstantPool, EMPTY_SOURCE_SPAN, Expression, ParseError, ParsedHostBindings, R3DirectiveMetadata, R3QueryMetadata, Statement, WrappedNodeExpr, compileDirectiveFromMetadata, makeBindingParser, parseHostBindings, verifyHostBindings} from '@angular/compiler';
|
import {ConstantPool, EMPTY_SOURCE_SPAN, Expression, Identifiers, ParseError, ParsedHostBindings, R3DirectiveMetadata, R3QueryMetadata, Statement, WrappedNodeExpr, compileDirectiveFromMetadata, makeBindingParser, parseHostBindings, verifyHostBindings} from '@angular/compiler';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||||
|
@ -94,7 +94,7 @@ export class DirectiveDecoratorHandler implements
|
||||||
CompileResult[] {
|
CompileResult[] {
|
||||||
const meta = analysis.meta;
|
const meta = analysis.meta;
|
||||||
const res = compileDirectiveFromMetadata(meta, pool, makeBindingParser());
|
const res = compileDirectiveFromMetadata(meta, pool, makeBindingParser());
|
||||||
const factoryRes = compileNgFactoryDefField(meta);
|
const factoryRes = compileNgFactoryDefField({...meta, injectFn: Identifiers.directiveInject});
|
||||||
if (analysis.metadataStmt !== null) {
|
if (analysis.metadataStmt !== null) {
|
||||||
factoryRes.statements.push(analysis.metadataStmt);
|
factoryRes.statements.push(analysis.metadataStmt);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Expression, LiteralExpr, R3DependencyMetadata, R3InjectableMetadata, R3ResolvedDependencyType, Statement, WrappedNodeExpr, compileInjectable as compileIvyInjectable} from '@angular/compiler';
|
import {Expression, Identifiers, LiteralExpr, R3DependencyMetadata, R3InjectableMetadata, R3ResolvedDependencyType, Statement, WrappedNodeExpr, compileInjectable as compileIvyInjectable} from '@angular/compiler';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||||
|
@ -14,12 +14,15 @@ import {DefaultImportRecorder} from '../../imports';
|
||||||
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
|
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
|
||||||
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../transform';
|
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../transform';
|
||||||
|
|
||||||
|
import {compileNgFactoryDefField} from './factory';
|
||||||
import {generateSetClassMetadataCall} from './metadata';
|
import {generateSetClassMetadataCall} from './metadata';
|
||||||
import {findAngularDecorator, getConstructorDependencies, getValidConstructorDependencies, validateConstructorDependencies} from './util';
|
import {findAngularDecorator, getConstructorDependencies, getValidConstructorDependencies, isAngularCore, unwrapForwardRef, validateConstructorDependencies} from './util';
|
||||||
|
|
||||||
export interface InjectableHandlerData {
|
export interface InjectableHandlerData {
|
||||||
meta: R3InjectableMetadata;
|
meta: R3InjectableMetadata;
|
||||||
metadataStmt: Statement|null;
|
metadataStmt: Statement|null;
|
||||||
|
ctorDeps: R3DependencyMetadata[]|'invalid'|null;
|
||||||
|
needsFactory: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -49,41 +52,65 @@ export class InjectableDecoratorHandler implements
|
||||||
}
|
}
|
||||||
|
|
||||||
analyze(node: ClassDeclaration, decorator: Decorator): AnalysisOutput<InjectableHandlerData> {
|
analyze(node: ClassDeclaration, decorator: Decorator): AnalysisOutput<InjectableHandlerData> {
|
||||||
|
const meta = extractInjectableMetadata(node, decorator, this.reflector);
|
||||||
|
const decorators = this.reflector.getDecoratorsOfDeclaration(node);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
analysis: {
|
analysis: {
|
||||||
meta: extractInjectableMetadata(
|
meta,
|
||||||
node, decorator, this.reflector, this.defaultImportRecorder, this.isCore,
|
ctorDeps: extractInjectableCtorDeps(
|
||||||
|
node, meta, decorator, this.reflector, this.defaultImportRecorder, this.isCore,
|
||||||
this.strictCtorDeps),
|
this.strictCtorDeps),
|
||||||
metadataStmt: generateSetClassMetadataCall(
|
metadataStmt: generateSetClassMetadataCall(
|
||||||
node, this.reflector, this.defaultImportRecorder, this.isCore),
|
node, this.reflector, this.defaultImportRecorder, this.isCore),
|
||||||
|
// Avoid generating multiple factories if a class has
|
||||||
|
// more Angular decorators, apart from Injectable.
|
||||||
|
needsFactory: !decorators ||
|
||||||
|
decorators.every(current => !isAngularCore(current) || current.name === 'Injectable')
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
compile(node: ClassDeclaration, analysis: InjectableHandlerData): CompileResult {
|
compile(node: ClassDeclaration, analysis: InjectableHandlerData): CompileResult[] {
|
||||||
const res = compileIvyInjectable(analysis.meta);
|
const res = compileIvyInjectable(analysis.meta);
|
||||||
const statements = res.statements;
|
const statements = res.statements;
|
||||||
if (analysis.metadataStmt !== null) {
|
const results: CompileResult[] = [];
|
||||||
statements.push(analysis.metadataStmt);
|
|
||||||
|
if (analysis.needsFactory) {
|
||||||
|
const meta = analysis.meta;
|
||||||
|
const factoryRes = compileNgFactoryDefField({
|
||||||
|
name: meta.name,
|
||||||
|
type: meta.type,
|
||||||
|
typeArgumentCount: meta.typeArgumentCount,
|
||||||
|
deps: analysis.ctorDeps,
|
||||||
|
injectFn: Identifiers.inject
|
||||||
|
});
|
||||||
|
if (analysis.metadataStmt !== null) {
|
||||||
|
factoryRes.statements.push(analysis.metadataStmt);
|
||||||
|
}
|
||||||
|
results.push(factoryRes);
|
||||||
}
|
}
|
||||||
return {
|
|
||||||
|
results.push({
|
||||||
name: 'ngInjectableDef',
|
name: 'ngInjectableDef',
|
||||||
initializer: res.expression, statements,
|
initializer: res.expression, statements,
|
||||||
type: res.type,
|
type: res.type,
|
||||||
};
|
});
|
||||||
|
|
||||||
|
return results;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read metadata from the `@Injectable` decorator and produce the `IvyInjectableMetadata`, the input
|
* Read metadata from the `@Injectable` decorator and produce the `IvyInjectableMetadata`, the
|
||||||
|
* input
|
||||||
* metadata needed to run `compileIvyInjectable`.
|
* metadata needed to run `compileIvyInjectable`.
|
||||||
*
|
*
|
||||||
* A `null` return value indicates this is @Injectable has invalid data.
|
* A `null` return value indicates this is @Injectable has invalid data.
|
||||||
*/
|
*/
|
||||||
function extractInjectableMetadata(
|
function extractInjectableMetadata(
|
||||||
clazz: ClassDeclaration, decorator: Decorator, reflector: ReflectionHost,
|
clazz: ClassDeclaration, decorator: Decorator,
|
||||||
defaultImportRecorder: DefaultImportRecorder, isCore: boolean,
|
reflector: ReflectionHost): R3InjectableMetadata {
|
||||||
strictCtorDeps: boolean): R3InjectableMetadata {
|
|
||||||
const name = clazz.name.text;
|
const name = clazz.name.text;
|
||||||
const type = new WrappedNodeExpr(clazz.name);
|
const type = new WrappedNodeExpr(clazz.name);
|
||||||
const typeArgumentCount = reflector.getGenericArityOfClass(clazz) || 0;
|
const typeArgumentCount = reflector.getGenericArityOfClass(clazz) || 0;
|
||||||
|
@ -92,53 +119,13 @@ function extractInjectableMetadata(
|
||||||
ErrorCode.DECORATOR_NOT_CALLED, decorator.node, '@Injectable must be called');
|
ErrorCode.DECORATOR_NOT_CALLED, decorator.node, '@Injectable must be called');
|
||||||
}
|
}
|
||||||
if (decorator.args.length === 0) {
|
if (decorator.args.length === 0) {
|
||||||
// Ideally, using @Injectable() would have the same effect as using @Injectable({...}), and be
|
|
||||||
// subject to the same validation. However, existing Angular code abuses @Injectable, applying
|
|
||||||
// it to things like abstract classes with constructors that were never meant for use with
|
|
||||||
// Angular's DI.
|
|
||||||
//
|
|
||||||
// To deal with this, @Injectable() without an argument is more lenient, and if the constructor
|
|
||||||
// signature does not work for DI then an ngInjectableDef that throws.
|
|
||||||
let ctorDeps: R3DependencyMetadata[]|'invalid'|null = null;
|
|
||||||
if (strictCtorDeps) {
|
|
||||||
ctorDeps = getValidConstructorDependencies(clazz, reflector, defaultImportRecorder, isCore);
|
|
||||||
} else {
|
|
||||||
const possibleCtorDeps =
|
|
||||||
getConstructorDependencies(clazz, reflector, defaultImportRecorder, isCore);
|
|
||||||
if (possibleCtorDeps !== null) {
|
|
||||||
if (possibleCtorDeps.deps !== null) {
|
|
||||||
// This use of @Injectable has valid constructor dependencies.
|
|
||||||
ctorDeps = possibleCtorDeps.deps;
|
|
||||||
} else {
|
|
||||||
// This use of @Injectable is technically invalid. Generate a factory function which
|
|
||||||
// throws
|
|
||||||
// an error.
|
|
||||||
// TODO(alxhub): log warnings for the bad use of @Injectable.
|
|
||||||
ctorDeps = 'invalid';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
type,
|
type,
|
||||||
typeArgumentCount,
|
typeArgumentCount,
|
||||||
providedIn: new LiteralExpr(null), ctorDeps,
|
providedIn: new LiteralExpr(null),
|
||||||
};
|
};
|
||||||
} else if (decorator.args.length === 1) {
|
} else if (decorator.args.length === 1) {
|
||||||
const rawCtorDeps = getConstructorDependencies(clazz, reflector, defaultImportRecorder, isCore);
|
|
||||||
let ctorDeps: R3DependencyMetadata[]|'invalid'|null = null;
|
|
||||||
|
|
||||||
// rawCtorDeps will be null if the class has no constructor.
|
|
||||||
if (rawCtorDeps !== null) {
|
|
||||||
if (rawCtorDeps.deps !== null) {
|
|
||||||
// A constructor existed and had valid dependencies.
|
|
||||||
ctorDeps = rawCtorDeps.deps;
|
|
||||||
} else {
|
|
||||||
// A constructor existed but had invalid dependencies.
|
|
||||||
ctorDeps = 'invalid';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const metaNode = decorator.args[0];
|
const metaNode = decorator.args[0];
|
||||||
// Firstly make sure the decorator argument is an inline literal - if not, it's illegal to
|
// Firstly make sure the decorator argument is an inline literal - if not, it's illegal to
|
||||||
// transport references from one location to another. This is the problem that lowering
|
// transport references from one location to another. This is the problem that lowering
|
||||||
|
@ -170,27 +157,25 @@ function extractInjectableMetadata(
|
||||||
name,
|
name,
|
||||||
type,
|
type,
|
||||||
typeArgumentCount,
|
typeArgumentCount,
|
||||||
ctorDeps,
|
|
||||||
providedIn,
|
providedIn,
|
||||||
useValue: new WrappedNodeExpr(meta.get('useValue') !),
|
useValue: new WrappedNodeExpr(unwrapForwardRef(meta.get('useValue') !, reflector)),
|
||||||
};
|
};
|
||||||
} else if (meta.has('useExisting')) {
|
} else if (meta.has('useExisting')) {
|
||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
type,
|
type,
|
||||||
typeArgumentCount,
|
typeArgumentCount,
|
||||||
ctorDeps,
|
|
||||||
providedIn,
|
providedIn,
|
||||||
useExisting: new WrappedNodeExpr(meta.get('useExisting') !),
|
useExisting: new WrappedNodeExpr(unwrapForwardRef(meta.get('useExisting') !, reflector)),
|
||||||
};
|
};
|
||||||
} else if (meta.has('useClass')) {
|
} else if (meta.has('useClass')) {
|
||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
type,
|
type,
|
||||||
typeArgumentCount,
|
typeArgumentCount,
|
||||||
ctorDeps,
|
|
||||||
providedIn,
|
providedIn,
|
||||||
useClass: new WrappedNodeExpr(meta.get('useClass') !), userDeps,
|
useClass: new WrappedNodeExpr(unwrapForwardRef(meta.get('useClass') !, reflector)),
|
||||||
|
userDeps,
|
||||||
};
|
};
|
||||||
} else if (meta.has('useFactory')) {
|
} else if (meta.has('useFactory')) {
|
||||||
// useFactory is special - the 'deps' property must be analyzed.
|
// useFactory is special - the 'deps' property must be analyzed.
|
||||||
|
@ -200,14 +185,10 @@ function extractInjectableMetadata(
|
||||||
type,
|
type,
|
||||||
typeArgumentCount,
|
typeArgumentCount,
|
||||||
providedIn,
|
providedIn,
|
||||||
useFactory: factory, ctorDeps, userDeps,
|
useFactory: factory, userDeps,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
if (strictCtorDeps) {
|
return {name, type, typeArgumentCount, providedIn};
|
||||||
// Since use* was not provided, validate the deps according to strictCtorDeps.
|
|
||||||
validateConstructorDependencies(clazz, rawCtorDeps);
|
|
||||||
}
|
|
||||||
return {name, type, typeArgumentCount, providedIn, ctorDeps};
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new FatalDiagnosticError(
|
throw new FatalDiagnosticError(
|
||||||
|
@ -215,7 +196,69 @@ function extractInjectableMetadata(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function extractInjectableCtorDeps(
|
||||||
|
clazz: ClassDeclaration, meta: R3InjectableMetadata, decorator: Decorator,
|
||||||
|
reflector: ReflectionHost, defaultImportRecorder: DefaultImportRecorder, isCore: boolean,
|
||||||
|
strictCtorDeps: boolean) {
|
||||||
|
if (decorator.args === null) {
|
||||||
|
throw new FatalDiagnosticError(
|
||||||
|
ErrorCode.DECORATOR_NOT_CALLED, decorator.node, '@Injectable must be called');
|
||||||
|
}
|
||||||
|
|
||||||
|
let ctorDeps: R3DependencyMetadata[]|'invalid'|null = null;
|
||||||
|
|
||||||
|
if (decorator.args.length === 0) {
|
||||||
|
// Ideally, using @Injectable() would have the same effect as using @Injectable({...}), and be
|
||||||
|
// subject to the same validation. However, existing Angular code abuses @Injectable, applying
|
||||||
|
// it to things like abstract classes with constructors that were never meant for use with
|
||||||
|
// Angular's DI.
|
||||||
|
//
|
||||||
|
// To deal with this, @Injectable() without an argument is more lenient, and if the
|
||||||
|
// constructor
|
||||||
|
// signature does not work for DI then an ngInjectableDef that throws.
|
||||||
|
if (strictCtorDeps) {
|
||||||
|
ctorDeps = getValidConstructorDependencies(clazz, reflector, defaultImportRecorder, isCore);
|
||||||
|
} else {
|
||||||
|
const possibleCtorDeps =
|
||||||
|
getConstructorDependencies(clazz, reflector, defaultImportRecorder, isCore);
|
||||||
|
if (possibleCtorDeps !== null) {
|
||||||
|
if (possibleCtorDeps.deps !== null) {
|
||||||
|
// This use of @Injectable has valid constructor dependencies.
|
||||||
|
ctorDeps = possibleCtorDeps.deps;
|
||||||
|
} else {
|
||||||
|
// This use of @Injectable is technically invalid. Generate a factory function which
|
||||||
|
// throws
|
||||||
|
// an error.
|
||||||
|
// TODO(alxhub): log warnings for the bad use of @Injectable.
|
||||||
|
ctorDeps = 'invalid';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctorDeps;
|
||||||
|
} else if (decorator.args.length === 1) {
|
||||||
|
const rawCtorDeps = getConstructorDependencies(clazz, reflector, defaultImportRecorder, isCore);
|
||||||
|
|
||||||
|
// rawCtorDeps will be null if the class has no constructor.
|
||||||
|
if (rawCtorDeps !== null) {
|
||||||
|
if (rawCtorDeps.deps !== null) {
|
||||||
|
// A constructor existed and had valid dependencies.
|
||||||
|
ctorDeps = rawCtorDeps.deps;
|
||||||
|
} else {
|
||||||
|
// A constructor existed but had invalid dependencies.
|
||||||
|
ctorDeps = 'invalid';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strictCtorDeps && !meta.useValue && !meta.useExisting && !meta.useClass &&
|
||||||
|
!meta.useFactory) {
|
||||||
|
// Since use* was not provided, validate the deps according to strictCtorDeps.
|
||||||
|
validateConstructorDependencies(clazz, rawCtorDeps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctorDeps;
|
||||||
|
}
|
||||||
|
|
||||||
function getDep(dep: ts.Expression, reflector: ReflectionHost): R3DependencyMetadata {
|
function getDep(dep: ts.Expression, reflector: ReflectionHost): R3DependencyMetadata {
|
||||||
const meta: R3DependencyMetadata = {
|
const meta: R3DependencyMetadata = {
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {R3PipeMetadata, Statement, WrappedNodeExpr, compilePipeFromMetadata} from '@angular/compiler';
|
import {Identifiers, R3PipeMetadata, Statement, WrappedNodeExpr, compilePipeFromMetadata} from '@angular/compiler';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||||
|
@ -109,7 +109,11 @@ export class PipeDecoratorHandler implements DecoratorHandler<PipeHandlerData, D
|
||||||
compile(node: ClassDeclaration, analysis: PipeHandlerData): CompileResult[] {
|
compile(node: ClassDeclaration, analysis: PipeHandlerData): CompileResult[] {
|
||||||
const meta = analysis.meta;
|
const meta = analysis.meta;
|
||||||
const res = compilePipeFromMetadata(meta);
|
const res = compilePipeFromMetadata(meta);
|
||||||
const factoryRes = compileNgFactoryDefField({...meta, isPipe: true});
|
const factoryRes = compileNgFactoryDefField({
|
||||||
|
...meta,
|
||||||
|
injectFn: Identifiers.directiveInject,
|
||||||
|
isPipe: true,
|
||||||
|
});
|
||||||
if (analysis.metadataStmt !== null) {
|
if (analysis.metadataStmt !== null) {
|
||||||
factoryRes.statements.push(analysis.metadataStmt);
|
factoryRes.statements.push(analysis.metadataStmt);
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,6 +53,7 @@ const CORE_SUPPORTED_SYMBOLS = new Map<string, string>([
|
||||||
['ɵɵdefineNgModule', 'ɵɵdefineNgModule'],
|
['ɵɵdefineNgModule', 'ɵɵdefineNgModule'],
|
||||||
['ɵɵsetNgModuleScope', 'ɵɵsetNgModuleScope'],
|
['ɵɵsetNgModuleScope', 'ɵɵsetNgModuleScope'],
|
||||||
['ɵɵinject', 'ɵɵinject'],
|
['ɵɵinject', 'ɵɵinject'],
|
||||||
|
['ɵɵFactoryDef', 'ɵɵFactoryDef'],
|
||||||
['ɵsetClassMetadata', 'setClassMetadata'],
|
['ɵsetClassMetadata', 'setClassMetadata'],
|
||||||
['ɵɵInjectableDef', 'ɵɵInjectableDef'],
|
['ɵɵInjectableDef', 'ɵɵInjectableDef'],
|
||||||
['ɵɵInjectorDef', 'ɵɵInjectorDef'],
|
['ɵɵInjectorDef', 'ɵɵInjectorDef'],
|
||||||
|
|
|
@ -353,10 +353,14 @@ export class IvyCompilation {
|
||||||
const compileMatchRes =
|
const compileMatchRes =
|
||||||
match.handler.compile(node as ClassDeclaration, match.analyzed.analysis, constantPool);
|
match.handler.compile(node as ClassDeclaration, match.analyzed.analysis, constantPool);
|
||||||
this.perf.stop(compileSpan);
|
this.perf.stop(compileSpan);
|
||||||
if (!Array.isArray(compileMatchRes)) {
|
if (Array.isArray(compileMatchRes)) {
|
||||||
|
compileMatchRes.forEach(result => {
|
||||||
|
if (!res.some(r => r.name === result.name)) {
|
||||||
|
res.push(result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (!res.some(result => result.name === compileMatchRes.name)) {
|
||||||
res.push(compileMatchRes);
|
res.push(compileMatchRes);
|
||||||
} else {
|
|
||||||
res.push(...compileMatchRes);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -66,4 +66,294 @@ describe('compiler compliance: dependency injection', () => {
|
||||||
expectEmit(result.source, factory, 'Incorrect factory');
|
expectEmit(result.source, factory, 'Incorrect factory');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should create a factory definition for an injectable', () => {
|
||||||
|
const files = {
|
||||||
|
app: {
|
||||||
|
'spec.ts': `
|
||||||
|
import {Injectable} from '@angular/core';
|
||||||
|
|
||||||
|
class MyDependency {}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MyService {
|
||||||
|
constructor(dep: MyDependency) {}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const factory = `
|
||||||
|
MyService.ngFactoryDef = function MyService_Factory(t) {
|
||||||
|
return new (t || MyService)($r3$.ɵɵinject(MyDependency));
|
||||||
|
}`;
|
||||||
|
|
||||||
|
const def = `
|
||||||
|
MyService.ngInjectableDef = $r3$.ɵɵdefineInjectable({
|
||||||
|
token: MyService,
|
||||||
|
factory: function(t) {
|
||||||
|
return MyService.ngFactoryDef(t);
|
||||||
|
},
|
||||||
|
providedIn: null
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = compile(files, angularFiles);
|
||||||
|
expectEmit(result.source, factory, 'Incorrect factory definition');
|
||||||
|
expectEmit(result.source, def, 'Incorrect injectable definition');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a single ngFactoryDef if the class has more than one decorator', () => {
|
||||||
|
const files = {
|
||||||
|
app: {
|
||||||
|
'spec.ts': `
|
||||||
|
import {Injectable, Pipe} from '@angular/core';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
@Pipe({name: 'my-pipe'})
|
||||||
|
export class MyPipe {
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = compile(files, angularFiles).source;
|
||||||
|
const matches = result.match(/MyPipe\.ngFactoryDef = function MyPipe_Factory/g);
|
||||||
|
expect(matches ? matches.length : 0).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delegate directly to the alternate factory when setting `useFactory` without `deps`',
|
||||||
|
() => {
|
||||||
|
const files = {
|
||||||
|
app: {
|
||||||
|
'spec.ts': `
|
||||||
|
import {Injectable} from '@angular/core';
|
||||||
|
|
||||||
|
class MyAlternateService {}
|
||||||
|
|
||||||
|
function alternateFactory() {
|
||||||
|
return new MyAlternateService();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
useFactory: alternateFactory
|
||||||
|
})
|
||||||
|
export class MyService {
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const def = `
|
||||||
|
MyService.ngInjectableDef = $r3$.ɵɵdefineInjectable({
|
||||||
|
token: MyService,
|
||||||
|
factory: function() {
|
||||||
|
return alternateFactory();
|
||||||
|
},
|
||||||
|
providedIn: null
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = compile(files, angularFiles);
|
||||||
|
expectEmit(result.source, def, 'Incorrect injectable definition');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not delegate directly to the alternate factory when setting `useFactory` with `deps`',
|
||||||
|
() => {
|
||||||
|
const files = {
|
||||||
|
app: {
|
||||||
|
'spec.ts': `
|
||||||
|
import {Injectable} from '@angular/core';
|
||||||
|
|
||||||
|
class SomeDep {}
|
||||||
|
class MyAlternateService {}
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
useFactory: () => new MyAlternateFactory(),
|
||||||
|
deps: [SomeDep]
|
||||||
|
})
|
||||||
|
export class MyService {
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const def = `
|
||||||
|
MyService.ngInjectableDef = $r3$.ɵɵdefineInjectable({
|
||||||
|
token: MyService,
|
||||||
|
factory: function MyService_Factory(t) {
|
||||||
|
var r = null;
|
||||||
|
if (t) {
|
||||||
|
(r = new t());
|
||||||
|
} else {
|
||||||
|
(r = (() => new MyAlternateFactory())($r3$.ɵɵinject(SomeDep)));
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
},
|
||||||
|
providedIn: null
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = compile(files, angularFiles);
|
||||||
|
expectEmit(result.source, def, 'Incorrect injectable definition');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delegate directly to the alternate class factory when setting `useClass` without `deps`',
|
||||||
|
() => {
|
||||||
|
const files = {
|
||||||
|
app: {
|
||||||
|
'spec.ts': `
|
||||||
|
import {Injectable} from '@angular/core';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
class MyAlternateService {}
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
useClass: MyAlternateService
|
||||||
|
})
|
||||||
|
export class MyService {
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const factory = `
|
||||||
|
MyService.ngInjectableDef = $r3$.ɵɵdefineInjectable({
|
||||||
|
token: MyService,
|
||||||
|
factory: function(t) {
|
||||||
|
return MyAlternateService.ngFactoryDef(t);
|
||||||
|
},
|
||||||
|
providedIn: null
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = compile(files, angularFiles);
|
||||||
|
expectEmit(result.source, factory, 'Incorrect factory definition');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not delegate directly to the alternate class when setting `useClass` with `deps`',
|
||||||
|
() => {
|
||||||
|
const files = {
|
||||||
|
app: {
|
||||||
|
'spec.ts': `
|
||||||
|
import {Injectable} from '@angular/core';
|
||||||
|
|
||||||
|
class SomeDep {}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
class MyAlternateService {}
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
useClass: MyAlternateService,
|
||||||
|
deps: [SomeDep]
|
||||||
|
})
|
||||||
|
export class MyService {
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const factory = `
|
||||||
|
MyService.ngInjectableDef = $r3$.ɵɵdefineInjectable({
|
||||||
|
token: MyService,
|
||||||
|
factory: function MyService_Factory(t) {
|
||||||
|
var r = null;
|
||||||
|
if (t) {
|
||||||
|
(r = new t());
|
||||||
|
} else {
|
||||||
|
(r = new MyAlternateService($r3$.ɵɵinject(SomeDep)));
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
},
|
||||||
|
providedIn: null
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = compile(files, angularFiles);
|
||||||
|
expectEmit(result.source, factory, 'Incorrect factory definition');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should unwrap forward refs when delegating to a different class', () => {
|
||||||
|
const files = {
|
||||||
|
app: {
|
||||||
|
'spec.ts': `
|
||||||
|
import {Injectable, forwardRef} from '@angular/core';
|
||||||
|
|
||||||
|
@Injectable({providedIn: 'root', useClass: forwardRef(() => SomeProviderImpl)})
|
||||||
|
abstract class SomeProvider {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
class SomeProviderImpl extends SomeProvider {
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const factory = `
|
||||||
|
SomeProvider.ngInjectableDef = $r3$.ɵɵdefineInjectable({
|
||||||
|
token: SomeProvider,
|
||||||
|
factory: function(t) {
|
||||||
|
return SomeProviderImpl.ngFactoryDef(t);
|
||||||
|
},
|
||||||
|
providedIn: 'root'
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = compile(files, angularFiles);
|
||||||
|
expectEmit(result.source, factory, 'Incorrect factory definition');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have the pipe factory take precedence over the injectable factory, if a class has multiple decorators',
|
||||||
|
() => {
|
||||||
|
const files = {
|
||||||
|
app: {
|
||||||
|
'spec.ts': `
|
||||||
|
import {Component, NgModule, Pipe, PipeTransform, Injectable} from '@angular/core';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
class Service {}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
@Pipe({name: 'myPipe'})
|
||||||
|
export class MyPipe implements PipeTransform {
|
||||||
|
constructor(service: Service) {}
|
||||||
|
transform(value: any, ...args: any[]) { return value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Pipe({name: 'myOtherPipe'})
|
||||||
|
@Injectable()
|
||||||
|
export class MyOtherPipe implements PipeTransform {
|
||||||
|
constructor(service: Service) {}
|
||||||
|
transform(value: any, ...args: any[]) { return value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'my-app',
|
||||||
|
template: '{{0 | myPipe | myOtherPipe}}'
|
||||||
|
})
|
||||||
|
export class MyApp {}
|
||||||
|
|
||||||
|
@NgModule({declarations: [MyPipe, MyOtherPipe, MyApp], declarations: [Service]})
|
||||||
|
export class MyModule {}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = compile(files, angularFiles);
|
||||||
|
const source = result.source;
|
||||||
|
|
||||||
|
const MyPipeFactory = `
|
||||||
|
MyPipe.ngFactoryDef = function MyPipe_Factory(t) { return new (t || MyPipe)($r3$.ɵɵdirectiveInject(Service)); };
|
||||||
|
`;
|
||||||
|
|
||||||
|
const MyOtherPipeFactory = `
|
||||||
|
MyOtherPipe.ngFactoryDef = function MyOtherPipe_Factory(t) { return new (t || MyOtherPipe)($r3$.ɵɵdirectiveInject(Service)); };
|
||||||
|
`;
|
||||||
|
|
||||||
|
expectEmit(source, MyPipeFactory, 'Invalid pipe factory function');
|
||||||
|
expectEmit(source, MyOtherPipeFactory, 'Invalid pipe factory function');
|
||||||
|
expect(source.match(/MyPipe\.ngFactoryDef =/g) !.length).toBe(1);
|
||||||
|
expect(source.match(/MyOtherPipe\.ngFactoryDef =/g) !.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -67,6 +67,8 @@ runInEachFileSystem(os => {
|
||||||
const dtsContents = env.getContents('test.d.ts');
|
const dtsContents = env.getContents('test.d.ts');
|
||||||
expect(dtsContents).toContain('static ngInjectableDef: i0.ɵɵInjectableDef<Dep>;');
|
expect(dtsContents).toContain('static ngInjectableDef: i0.ɵɵInjectableDef<Dep>;');
|
||||||
expect(dtsContents).toContain('static ngInjectableDef: i0.ɵɵInjectableDef<Service>;');
|
expect(dtsContents).toContain('static ngInjectableDef: i0.ɵɵInjectableDef<Service>;');
|
||||||
|
expect(dtsContents).toContain('static ngFactoryDef: i0.ɵɵFactoryDef<Dep>;');
|
||||||
|
expect(dtsContents).toContain('static ngFactoryDef: i0.ɵɵFactoryDef<Service>;');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should compile Injectables with a generic service', () => {
|
it('should compile Injectables with a generic service', () => {
|
||||||
|
@ -83,6 +85,7 @@ runInEachFileSystem(os => {
|
||||||
const jsContents = env.getContents('test.js');
|
const jsContents = env.getContents('test.js');
|
||||||
expect(jsContents).toContain('Store.ngInjectableDef =');
|
expect(jsContents).toContain('Store.ngInjectableDef =');
|
||||||
const dtsContents = env.getContents('test.d.ts');
|
const dtsContents = env.getContents('test.d.ts');
|
||||||
|
expect(dtsContents).toContain('static ngFactoryDef: i0.ɵɵFactoryDef<Store<any>>;');
|
||||||
expect(dtsContents).toContain('static ngInjectableDef: i0.ɵɵInjectableDef<Store<any>>;');
|
expect(dtsContents).toContain('static ngInjectableDef: i0.ɵɵInjectableDef<Store<any>>;');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -106,11 +109,15 @@ runInEachFileSystem(os => {
|
||||||
expect(jsContents).toContain('Dep.ngInjectableDef =');
|
expect(jsContents).toContain('Dep.ngInjectableDef =');
|
||||||
expect(jsContents).toContain('Service.ngInjectableDef =');
|
expect(jsContents).toContain('Service.ngInjectableDef =');
|
||||||
expect(jsContents)
|
expect(jsContents)
|
||||||
.toContain('return new (t || Service)(i0.ɵɵinject(Dep)); }, providedIn: \'root\' });');
|
.toContain(
|
||||||
|
'Service.ngFactoryDef = function Service_Factory(t) { return new (t || Service)(i0.ɵɵinject(Dep)); };');
|
||||||
|
expect(jsContents).toContain('providedIn: \'root\' })');
|
||||||
expect(jsContents).not.toContain('__decorate');
|
expect(jsContents).not.toContain('__decorate');
|
||||||
const dtsContents = env.getContents('test.d.ts');
|
const dtsContents = env.getContents('test.d.ts');
|
||||||
expect(dtsContents).toContain('static ngInjectableDef: i0.ɵɵInjectableDef<Dep>;');
|
expect(dtsContents).toContain('static ngInjectableDef: i0.ɵɵInjectableDef<Dep>;');
|
||||||
expect(dtsContents).toContain('static ngInjectableDef: i0.ɵɵInjectableDef<Service>;');
|
expect(dtsContents).toContain('static ngInjectableDef: i0.ɵɵInjectableDef<Service>;');
|
||||||
|
expect(dtsContents).toContain('static ngFactoryDef: i0.ɵɵFactoryDef<Dep>;');
|
||||||
|
expect(dtsContents).toContain('static ngFactoryDef: i0.ɵɵFactoryDef<Service>;');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should compile Injectables with providedIn and factory without errors', () => {
|
it('should compile Injectables with providedIn and factory without errors', () => {
|
||||||
|
@ -128,13 +135,14 @@ runInEachFileSystem(os => {
|
||||||
|
|
||||||
const jsContents = env.getContents('test.js');
|
const jsContents = env.getContents('test.js');
|
||||||
expect(jsContents).toContain('Service.ngInjectableDef =');
|
expect(jsContents).toContain('Service.ngInjectableDef =');
|
||||||
expect(jsContents).toContain('(r = new t());');
|
expect(jsContents)
|
||||||
expect(jsContents).toContain('(r = (function () { return new Service(); })());');
|
.toContain('factory: function () { return (function () { return new Service(); })(); }');
|
||||||
expect(jsContents).toContain('factory: function Service_Factory(t) { var r = null; if (t) {');
|
expect(jsContents).toContain('Service_Factory(t) { return new (t || Service)(); }');
|
||||||
expect(jsContents).toContain('return r; }, providedIn: \'root\' });');
|
expect(jsContents).toContain(', providedIn: \'root\' });');
|
||||||
expect(jsContents).not.toContain('__decorate');
|
expect(jsContents).not.toContain('__decorate');
|
||||||
const dtsContents = env.getContents('test.d.ts');
|
const dtsContents = env.getContents('test.d.ts');
|
||||||
expect(dtsContents).toContain('static ngInjectableDef: i0.ɵɵInjectableDef<Service>;');
|
expect(dtsContents).toContain('static ngInjectableDef: i0.ɵɵInjectableDef<Service>;');
|
||||||
|
expect(dtsContents).toContain('static ngFactoryDef: i0.ɵɵFactoryDef<Service>;');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should compile Injectables with providedIn and factory with deps without errors', () => {
|
it('should compile Injectables with providedIn and factory with deps without errors', () => {
|
||||||
|
@ -156,13 +164,14 @@ runInEachFileSystem(os => {
|
||||||
const jsContents = env.getContents('test.js');
|
const jsContents = env.getContents('test.js');
|
||||||
expect(jsContents).toContain('Service.ngInjectableDef =');
|
expect(jsContents).toContain('Service.ngInjectableDef =');
|
||||||
expect(jsContents).toContain('factory: function Service_Factory(t) { var r = null; if (t) {');
|
expect(jsContents).toContain('factory: function Service_Factory(t) { var r = null; if (t) {');
|
||||||
expect(jsContents).toContain('(r = new t(i0.ɵɵinject(Dep)));');
|
expect(jsContents).toContain('return new (t || Service)(i0.ɵɵinject(Dep));');
|
||||||
expect(jsContents)
|
expect(jsContents)
|
||||||
.toContain('(r = (function (dep) { return new Service(dep); })(i0.ɵɵinject(Dep)));');
|
.toContain('(r = (function (dep) { return new Service(dep); })(i0.ɵɵinject(Dep)));');
|
||||||
expect(jsContents).toContain('return r; }, providedIn: \'root\' });');
|
expect(jsContents).toContain('return r; }, providedIn: \'root\' });');
|
||||||
expect(jsContents).not.toContain('__decorate');
|
expect(jsContents).not.toContain('__decorate');
|
||||||
const dtsContents = env.getContents('test.d.ts');
|
const dtsContents = env.getContents('test.d.ts');
|
||||||
expect(dtsContents).toContain('static ngInjectableDef: i0.ɵɵInjectableDef<Service>;');
|
expect(dtsContents).toContain('static ngInjectableDef: i0.ɵɵInjectableDef<Service>;');
|
||||||
|
expect(dtsContents).toContain('static ngFactoryDef: i0.ɵɵFactoryDef<Service>;');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should compile @Injectable with an @Optional dependency', () => {
|
it('should compile @Injectable with an @Optional dependency', () => {
|
||||||
|
@ -1282,7 +1291,7 @@ runInEachFileSystem(os => {
|
||||||
|
|
||||||
env.driveMain();
|
env.driveMain();
|
||||||
const jsContents = env.getContents('test.js');
|
const jsContents = env.getContents('test.js');
|
||||||
expect(jsContents).toMatch(/if \(t\).*throw new Error.* else .* '42'/ms);
|
expect(jsContents).toMatch(/function Test_Factory\(t\) { throw new Error\(/ms);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1290,33 +1299,35 @@ runInEachFileSystem(os => {
|
||||||
it('should compile an @Injectable on a class with a non-injectable constructor', () => {
|
it('should compile an @Injectable on a class with a non-injectable constructor', () => {
|
||||||
env.tsconfig({strictInjectionParameters: false});
|
env.tsconfig({strictInjectionParameters: false});
|
||||||
env.write('test.ts', `
|
env.write('test.ts', `
|
||||||
import {Injectable} from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class Test {
|
export class Test {
|
||||||
constructor(private notInjectable: string) {}
|
constructor(private notInjectable: string) {}
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
env.driveMain();
|
env.driveMain();
|
||||||
const jsContents = env.getContents('test.js');
|
const jsContents = env.getContents('test.js');
|
||||||
expect(jsContents).toContain('factory: function Test_Factory(t) { throw new Error(');
|
expect(jsContents)
|
||||||
|
.toContain('Test.ngFactoryDef = function Test_Factory(t) { throw new Error(');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should compile an @Injectable provided in the root on a class with a non-injectable constructor',
|
it('should compile an @Injectable provided in the root on a class with a non-injectable constructor',
|
||||||
() => {
|
() => {
|
||||||
env.tsconfig({strictInjectionParameters: false});
|
env.tsconfig({strictInjectionParameters: false});
|
||||||
env.write('test.ts', `
|
env.write('test.ts', `
|
||||||
import {Injectable} from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
@Injectable({providedIn: 'root'})
|
@Injectable({providedIn: 'root'})
|
||||||
export class Test {
|
export class Test {
|
||||||
constructor(private notInjectable: string) {}
|
constructor(private notInjectable: string) {}
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
env.driveMain();
|
env.driveMain();
|
||||||
const jsContents = env.getContents('test.js');
|
const jsContents = env.getContents('test.js');
|
||||||
expect(jsContents).toContain('factory: function Test_Factory(t) { throw new Error(');
|
expect(jsContents)
|
||||||
|
.toContain('Test.ngFactoryDef = function Test_Factory(t) { throw new Error(');
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -40,9 +40,7 @@ export interface CompilerFacade {
|
||||||
compileBase(angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3BaseMetadataFacade):
|
compileBase(angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3BaseMetadataFacade):
|
||||||
any;
|
any;
|
||||||
compileFactory(
|
compileFactory(
|
||||||
angularCoreEnv: CoreEnvironment, sourceMapUrl: string,
|
angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3FactoryDefMetadataFacade): any;
|
||||||
meta: R3PipeMetadataFacade|R3DirectiveMetadataFacade|R3ComponentMetadataFacade,
|
|
||||||
isPipe?: boolean): any;
|
|
||||||
|
|
||||||
createParseSourceSpan(kind: string, typeName: string, sourceUrl: string): ParseSourceSpan;
|
createParseSourceSpan(kind: string, typeName: string, sourceUrl: string): ParseSourceSpan;
|
||||||
|
|
||||||
|
@ -94,7 +92,6 @@ export interface R3InjectableMetadataFacade {
|
||||||
name: string;
|
name: string;
|
||||||
type: any;
|
type: any;
|
||||||
typeArgumentCount: number;
|
typeArgumentCount: number;
|
||||||
ctorDeps: R3DependencyMetadataFacade[]|null;
|
|
||||||
providedIn: any;
|
providedIn: any;
|
||||||
useClass?: any;
|
useClass?: any;
|
||||||
useFactory?: any;
|
useFactory?: any;
|
||||||
|
@ -164,6 +161,15 @@ export interface R3BaseMetadataFacade {
|
||||||
viewQueries?: R3QueryMetadataFacade[];
|
viewQueries?: R3QueryMetadataFacade[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface R3FactoryDefMetadataFacade {
|
||||||
|
name: string;
|
||||||
|
type: any;
|
||||||
|
typeArgumentCount: number;
|
||||||
|
deps: R3DependencyMetadataFacade[]|null;
|
||||||
|
injectFn: 'directiveInject'|'inject';
|
||||||
|
isPipe: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export type ViewEncapsulation = number;
|
export type ViewEncapsulation = number;
|
||||||
|
|
||||||
export type ChangeDetectionStrategy = number;
|
export type ChangeDetectionStrategy = number;
|
||||||
|
|
|
@ -63,6 +63,7 @@ export class Identifiers {
|
||||||
|
|
||||||
};
|
};
|
||||||
static inject: o.ExternalReference = {name: 'ɵɵinject', moduleName: CORE};
|
static inject: o.ExternalReference = {name: 'ɵɵinject', moduleName: CORE};
|
||||||
|
static directiveInject: o.ExternalReference = {name: 'ɵɵdirectiveInject', moduleName: CORE};
|
||||||
static INJECTOR: o.ExternalReference = {name: 'INJECTOR', moduleName: CORE};
|
static INJECTOR: o.ExternalReference = {name: 'INJECTOR', moduleName: CORE};
|
||||||
static Injector: o.ExternalReference = {name: 'Injector', moduleName: CORE};
|
static Injector: o.ExternalReference = {name: 'Injector', moduleName: CORE};
|
||||||
static ɵɵdefineInjectable: o.ExternalReference = {name: 'ɵɵdefineInjectable', moduleName: CORE};
|
static ɵɵdefineInjectable: o.ExternalReference = {name: 'ɵɵdefineInjectable', moduleName: CORE};
|
||||||
|
|
|
@ -6,10 +6,9 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {InjectFlags} from './core';
|
|
||||||
import {Identifiers} from './identifiers';
|
import {Identifiers} from './identifiers';
|
||||||
import * as o from './output/output_ast';
|
import * as o from './output/output_ast';
|
||||||
import {R3DependencyMetadata, R3FactoryDelegateType, R3FactoryMetadata, compileFactoryFunction} from './render3/r3_factory';
|
import {R3DependencyMetadata, R3FactoryDelegateType, compileFactoryFunction} from './render3/r3_factory';
|
||||||
import {mapToMapExpression, typeWithParameters} from './render3/util';
|
import {mapToMapExpression, typeWithParameters} from './render3/util';
|
||||||
|
|
||||||
export interface InjectableDef {
|
export interface InjectableDef {
|
||||||
|
@ -22,7 +21,6 @@ export interface R3InjectableMetadata {
|
||||||
name: string;
|
name: string;
|
||||||
type: o.Expression;
|
type: o.Expression;
|
||||||
typeArgumentCount: number;
|
typeArgumentCount: number;
|
||||||
ctorDeps: R3DependencyMetadata[]|'invalid'|null;
|
|
||||||
providedIn: o.Expression;
|
providedIn: o.Expression;
|
||||||
useClass?: o.Expression;
|
useClass?: o.Expression;
|
||||||
useFactory?: o.Expression;
|
useFactory?: o.Expression;
|
||||||
|
@ -38,7 +36,7 @@ export function compileInjectable(meta: R3InjectableMetadata): InjectableDef {
|
||||||
name: meta.name,
|
name: meta.name,
|
||||||
type: meta.type,
|
type: meta.type,
|
||||||
typeArgumentCount: meta.typeArgumentCount,
|
typeArgumentCount: meta.typeArgumentCount,
|
||||||
deps: meta.ctorDeps,
|
deps: [],
|
||||||
injectFn: Identifiers.inject,
|
injectFn: Identifiers.inject,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -67,19 +65,22 @@ export function compileInjectable(meta: R3InjectableMetadata): InjectableDef {
|
||||||
} else if (useClassOnSelf) {
|
} else if (useClassOnSelf) {
|
||||||
result = compileFactoryFunction(factoryMeta);
|
result = compileFactoryFunction(factoryMeta);
|
||||||
} else {
|
} else {
|
||||||
result = compileFactoryFunction({
|
result = delegateToFactory(meta.useClass);
|
||||||
...factoryMeta,
|
|
||||||
delegate: meta.useClass,
|
|
||||||
delegateType: R3FactoryDelegateType.Factory,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} else if (meta.useFactory !== undefined) {
|
} else if (meta.useFactory !== undefined) {
|
||||||
result = compileFactoryFunction({
|
if (meta.userDeps !== undefined) {
|
||||||
...factoryMeta,
|
result = compileFactoryFunction({
|
||||||
delegate: meta.useFactory,
|
...factoryMeta,
|
||||||
delegateDeps: meta.userDeps || [],
|
delegate: meta.useFactory,
|
||||||
delegateType: R3FactoryDelegateType.Function,
|
delegateDeps: meta.userDeps || [],
|
||||||
});
|
delegateType: R3FactoryDelegateType.Function,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
result = {
|
||||||
|
statements: [],
|
||||||
|
factory: o.fn([], [new o.ReturnStatement(meta.useFactory.callFn([]))])
|
||||||
|
};
|
||||||
|
}
|
||||||
} else if (meta.useValue !== undefined) {
|
} else if (meta.useValue !== undefined) {
|
||||||
// Note: it's safe to use `meta.useValue` instead of the `USE_VALUE in meta` check used for
|
// 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
|
// client code because meta.useValue is an Expression which will be defined even if the actual
|
||||||
|
@ -95,7 +96,7 @@ export function compileInjectable(meta: R3InjectableMetadata): InjectableDef {
|
||||||
expression: o.importExpr(Identifiers.inject).callFn([meta.useExisting]),
|
expression: o.importExpr(Identifiers.inject).callFn([meta.useExisting]),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
result = compileFactoryFunction(factoryMeta);
|
result = delegateToFactory(meta.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = meta.type;
|
const token = meta.type;
|
||||||
|
@ -112,3 +113,12 @@ export function compileInjectable(meta: R3InjectableMetadata): InjectableDef {
|
||||||
statements: result.statements,
|
statements: result.statements,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function delegateToFactory(type: o.Expression) {
|
||||||
|
return {
|
||||||
|
statements: [],
|
||||||
|
// () => meta.type.ngFactoryDef(t)
|
||||||
|
factory: o.fn([new o.FnParam('t', o.DYNAMIC_TYPE)], [new o.ReturnStatement(type.callMethod(
|
||||||
|
'ngFactoryDef', [o.variable('t')]))])
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -7,9 +7,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
import {CompilerFacade, CoreEnvironment, ExportedCompilerFacade, R3BaseMetadataFacade, R3ComponentMetadataFacade, R3DependencyMetadataFacade, R3DirectiveMetadataFacade, R3InjectableMetadataFacade, R3InjectorMetadataFacade, R3NgModuleMetadataFacade, R3PipeMetadataFacade, R3QueryMetadataFacade, StringMap, StringMapWithRename} from './compiler_facade_interface';
|
import {CompilerFacade, CoreEnvironment, ExportedCompilerFacade, R3BaseMetadataFacade, R3ComponentMetadataFacade, R3DependencyMetadataFacade, R3DirectiveMetadataFacade, R3FactoryDefMetadataFacade, R3InjectableMetadataFacade, R3InjectorMetadataFacade, R3NgModuleMetadataFacade, R3PipeMetadataFacade, R3QueryMetadataFacade, StringMap, StringMapWithRename} from './compiler_facade_interface';
|
||||||
import {ConstantPool} from './constant_pool';
|
import {ConstantPool} from './constant_pool';
|
||||||
import {HostBinding, HostListener, Input, Output, Type} from './core';
|
import {HostBinding, HostListener, Input, Output, Type} from './core';
|
||||||
|
import {Identifiers} from './identifiers';
|
||||||
import {compileInjectable} from './injectable_compiler_2';
|
import {compileInjectable} from './injectable_compiler_2';
|
||||||
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from './ml_parser/interpolation_config';
|
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from './ml_parser/interpolation_config';
|
||||||
import {DeclareVarStmt, Expression, LiteralExpr, Statement, StmtModifier, WrappedNodeExpr} from './output/output_ast';
|
import {DeclareVarStmt, Expression, LiteralExpr, Statement, StmtModifier, WrappedNodeExpr} from './output/output_ast';
|
||||||
|
@ -59,7 +60,6 @@ export class CompilerFacadeImpl implements CompilerFacade {
|
||||||
useFactory: wrapExpression(facade, USE_FACTORY),
|
useFactory: wrapExpression(facade, USE_FACTORY),
|
||||||
useValue: wrapExpression(facade, USE_VALUE),
|
useValue: wrapExpression(facade, USE_VALUE),
|
||||||
useExisting: wrapExpression(facade, USE_EXISTING),
|
useExisting: wrapExpression(facade, USE_EXISTING),
|
||||||
ctorDeps: convertR3DependencyMetadataArray(facade.ctorDeps),
|
|
||||||
userDeps: convertR3DependencyMetadataArray(facade.userDeps) || undefined,
|
userDeps: convertR3DependencyMetadataArray(facade.userDeps) || undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -154,14 +154,15 @@ export class CompilerFacadeImpl implements CompilerFacade {
|
||||||
}
|
}
|
||||||
|
|
||||||
compileFactory(
|
compileFactory(
|
||||||
angularCoreEnv: CoreEnvironment, sourceMapUrl: string,
|
angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3FactoryDefMetadataFacade) {
|
||||||
meta: R3PipeMetadataFacade|R3DirectiveMetadataFacade|R3ComponentMetadataFacade,
|
|
||||||
isPipe = false) {
|
|
||||||
const factoryRes = compileFactoryFromMetadata({
|
const factoryRes = compileFactoryFromMetadata({
|
||||||
name: meta.name,
|
name: meta.name,
|
||||||
type: new WrappedNodeExpr(meta.type),
|
type: new WrappedNodeExpr(meta.type),
|
||||||
typeArgumentCount: meta.typeArgumentCount,
|
typeArgumentCount: meta.typeArgumentCount,
|
||||||
deps: convertR3DependencyMetadataArray(meta.deps), isPipe
|
deps: convertR3DependencyMetadataArray(meta.deps),
|
||||||
|
injectFn: meta.injectFn === 'directiveInject' ? Identifiers.directiveInject :
|
||||||
|
Identifiers.inject,
|
||||||
|
isPipe: meta.isPipe
|
||||||
});
|
});
|
||||||
return this.jitExpression(
|
return this.jitExpression(
|
||||||
factoryRes.factory, angularCoreEnv, sourceMapUrl, factoryRes.statements);
|
factoryRes.factory, angularCoreEnv, sourceMapUrl, factoryRes.statements);
|
||||||
|
|
|
@ -86,7 +86,8 @@ export interface R3FactoryDefMetadata {
|
||||||
name: string;
|
name: string;
|
||||||
type: o.Expression;
|
type: o.Expression;
|
||||||
typeArgumentCount: number;
|
typeArgumentCount: number;
|
||||||
deps: R3DependencyMetadata[]|null;
|
deps: R3DependencyMetadata[]|'invalid'|null;
|
||||||
|
injectFn: o.ExternalReference;
|
||||||
isPipe?: boolean;
|
isPipe?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,8 +268,7 @@ export function compileFactoryFromMetadata(meta: R3FactoryDefMetadata): R3Factor
|
||||||
type: meta.type,
|
type: meta.type,
|
||||||
deps: meta.deps,
|
deps: meta.deps,
|
||||||
typeArgumentCount: meta.typeArgumentCount,
|
typeArgumentCount: meta.typeArgumentCount,
|
||||||
// TODO(crisbeto): this should be refactored once we start using it for injectables.
|
injectFn: meta.injectFn,
|
||||||
injectFn: R3.directiveInject,
|
|
||||||
},
|
},
|
||||||
meta.isPipe);
|
meta.isPipe);
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,7 +89,8 @@ export function compilePipeFromRender2(
|
||||||
pure: pipe.pure,
|
pure: pipe.pure,
|
||||||
};
|
};
|
||||||
const res = compilePipeFromMetadata(metadata);
|
const res = compilePipeFromMetadata(metadata);
|
||||||
const factoryRes = compileFactoryFromMetadata({...metadata, isPipe: true});
|
const factoryRes =
|
||||||
|
compileFactoryFromMetadata({...metadata, injectFn: R3.directiveInject, isPipe: true});
|
||||||
const definitionField = outputCtx.constantPool.propertyNameOf(DefinitionKind.Pipe);
|
const definitionField = outputCtx.constantPool.propertyNameOf(DefinitionKind.Pipe);
|
||||||
const ngFactoryDefStatement = new o.ClassStmt(
|
const ngFactoryDefStatement = new o.ClassStmt(
|
||||||
/* name */ name,
|
/* name */ name,
|
||||||
|
|
|
@ -325,7 +325,7 @@ export function compileDirectiveFromRender2(
|
||||||
|
|
||||||
const meta = directiveMetadataFromGlobalMetadata(directive, outputCtx, reflector);
|
const meta = directiveMetadataFromGlobalMetadata(directive, outputCtx, reflector);
|
||||||
const res = compileDirectiveFromMetadata(meta, outputCtx.constantPool, bindingParser);
|
const res = compileDirectiveFromMetadata(meta, outputCtx.constantPool, bindingParser);
|
||||||
const factoryRes = compileFactoryFromMetadata(meta);
|
const factoryRes = compileFactoryFromMetadata({...meta, injectFn: R3.directiveInject});
|
||||||
const ngFactoryDefStatement = new o.ClassStmt(
|
const ngFactoryDefStatement = new o.ClassStmt(
|
||||||
name, null,
|
name, null,
|
||||||
[new o.ClassField(
|
[new o.ClassField(
|
||||||
|
@ -378,7 +378,7 @@ export function compileComponentFromRender2(
|
||||||
i18nUseExternalIds: true,
|
i18nUseExternalIds: true,
|
||||||
};
|
};
|
||||||
const res = compileComponentFromMetadata(meta, outputCtx.constantPool, bindingParser);
|
const res = compileComponentFromMetadata(meta, outputCtx.constantPool, bindingParser);
|
||||||
const factoryRes = compileFactoryFromMetadata(meta);
|
const factoryRes = compileFactoryFromMetadata({...meta, injectFn: R3.directiveInject});
|
||||||
const ngFactoryDefStatement = new o.ClassStmt(
|
const ngFactoryDefStatement = new o.ClassStmt(
|
||||||
name, null,
|
name, null,
|
||||||
[new o.ClassField(
|
[new o.ClassField(
|
||||||
|
|
|
@ -40,9 +40,7 @@ export interface CompilerFacade {
|
||||||
compileBase(angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3BaseMetadataFacade):
|
compileBase(angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3BaseMetadataFacade):
|
||||||
any;
|
any;
|
||||||
compileFactory(
|
compileFactory(
|
||||||
angularCoreEnv: CoreEnvironment, sourceMapUrl: string,
|
angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3FactoryDefMetadataFacade): any;
|
||||||
meta: R3PipeMetadataFacade|R3DirectiveMetadataFacade|R3ComponentMetadataFacade,
|
|
||||||
isPipe?: boolean): any;
|
|
||||||
|
|
||||||
createParseSourceSpan(kind: string, typeName: string, sourceUrl: string): ParseSourceSpan;
|
createParseSourceSpan(kind: string, typeName: string, sourceUrl: string): ParseSourceSpan;
|
||||||
|
|
||||||
|
@ -94,7 +92,6 @@ export interface R3InjectableMetadataFacade {
|
||||||
name: string;
|
name: string;
|
||||||
type: any;
|
type: any;
|
||||||
typeArgumentCount: number;
|
typeArgumentCount: number;
|
||||||
ctorDeps: R3DependencyMetadataFacade[]|null;
|
|
||||||
providedIn: any;
|
providedIn: any;
|
||||||
useClass?: any;
|
useClass?: any;
|
||||||
useFactory?: any;
|
useFactory?: any;
|
||||||
|
@ -164,6 +161,15 @@ export interface R3BaseMetadataFacade {
|
||||||
viewQueries?: R3QueryMetadataFacade[];
|
viewQueries?: R3QueryMetadataFacade[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface R3FactoryDefMetadataFacade {
|
||||||
|
name: string;
|
||||||
|
type: any;
|
||||||
|
typeArgumentCount: number;
|
||||||
|
deps: R3DependencyMetadataFacade[]|null;
|
||||||
|
injectFn: 'directiveInject'|'inject';
|
||||||
|
isPipe: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export type ViewEncapsulation = number;
|
export type ViewEncapsulation = number;
|
||||||
|
|
||||||
export type ChangeDetectionStrategy = number;
|
export type ChangeDetectionStrategy = number;
|
||||||
|
|
|
@ -8,7 +8,9 @@
|
||||||
|
|
||||||
import {R3InjectableMetadataFacade, getCompilerFacade} from '../../compiler/compiler_facade';
|
import {R3InjectableMetadataFacade, getCompilerFacade} from '../../compiler/compiler_facade';
|
||||||
import {Type} from '../../interface/type';
|
import {Type} from '../../interface/type';
|
||||||
|
import {NG_FACTORY_DEF} from '../../render3/fields';
|
||||||
import {getClosureSafeProperty} from '../../util/property';
|
import {getClosureSafeProperty} from '../../util/property';
|
||||||
|
import {resolveForwardRef} from '../forward_ref';
|
||||||
import {Injectable} from '../injectable';
|
import {Injectable} from '../injectable';
|
||||||
import {NG_INJECTABLE_DEF} from '../interface/defs';
|
import {NG_INJECTABLE_DEF} from '../interface/defs';
|
||||||
import {ClassSansProvider, ExistingSansProvider, FactorySansProvider, ValueProvider, ValueSansProvider} from '../interface/provider';
|
import {ClassSansProvider, ExistingSansProvider, FactorySansProvider, ValueProvider, ValueSansProvider} from '../interface/provider';
|
||||||
|
@ -23,59 +25,45 @@ import {convertDependencies, reflectDependencies} from './util';
|
||||||
* `ngInjectableDef` onto the injectable type.
|
* `ngInjectableDef` onto the injectable type.
|
||||||
*/
|
*/
|
||||||
export function compileInjectable(type: Type<any>, srcMeta?: Injectable): void {
|
export function compileInjectable(type: Type<any>, srcMeta?: Injectable): void {
|
||||||
let def: any = null;
|
let ngInjectableDef: any = null;
|
||||||
|
let ngFactoryDef: any = null;
|
||||||
|
|
||||||
// if NG_INJECTABLE_DEF is already defined on this class then don't overwrite it
|
// if NG_INJECTABLE_DEF is already defined on this class then don't overwrite it
|
||||||
if (type.hasOwnProperty(NG_INJECTABLE_DEF)) return;
|
if (!type.hasOwnProperty(NG_INJECTABLE_DEF)) {
|
||||||
|
Object.defineProperty(type, NG_INJECTABLE_DEF, {
|
||||||
Object.defineProperty(type, NG_INJECTABLE_DEF, {
|
get: () => {
|
||||||
get: () => {
|
if (ngInjectableDef === null) {
|
||||||
if (def === null) {
|
ngInjectableDef = getCompilerFacade().compileInjectable(
|
||||||
// Allow the compilation of a class with a `@Injectable()` decorator without parameters
|
angularCoreDiEnv, `ng:///${type.name}/ngInjectableDef.js`,
|
||||||
const meta: Injectable = srcMeta || {providedIn: null};
|
getInjectableMetadata(type, srcMeta));
|
||||||
const hasAProvider = isUseClassProvider(meta) || isUseFactoryProvider(meta) ||
|
|
||||||
isUseValueProvider(meta) || isUseExistingProvider(meta);
|
|
||||||
|
|
||||||
|
|
||||||
const compilerMeta: R3InjectableMetadataFacade = {
|
|
||||||
name: type.name,
|
|
||||||
type: type,
|
|
||||||
typeArgumentCount: 0,
|
|
||||||
providedIn: meta.providedIn,
|
|
||||||
ctorDeps: reflectDependencies(type),
|
|
||||||
userDeps: undefined,
|
|
||||||
};
|
|
||||||
if ((isUseClassProvider(meta) || isUseFactoryProvider(meta)) && meta.deps !== undefined) {
|
|
||||||
compilerMeta.userDeps = convertDependencies(meta.deps);
|
|
||||||
}
|
}
|
||||||
if (!hasAProvider) {
|
return ngInjectableDef;
|
||||||
// In the case the user specifies a type provider, treat it as {provide: X, useClass: X}.
|
},
|
||||||
// The deps will have been reflected above, causing the factory to create the class by
|
});
|
||||||
// calling
|
}
|
||||||
// its constructor with injected deps.
|
|
||||||
compilerMeta.useClass = type;
|
// if NG_FACTORY_DEF is already defined on this class then don't overwrite it
|
||||||
} else if (isUseClassProvider(meta)) {
|
if (!type.hasOwnProperty(NG_FACTORY_DEF)) {
|
||||||
// The user explicitly specified useClass, and may or may not have provided deps.
|
Object.defineProperty(type, NG_FACTORY_DEF, {
|
||||||
compilerMeta.useClass = meta.useClass;
|
get: () => {
|
||||||
} else if (isUseValueProvider(meta)) {
|
if (ngFactoryDef === null) {
|
||||||
// The user explicitly specified useValue.
|
const metadata = getInjectableMetadata(type, srcMeta);
|
||||||
compilerMeta.useValue = meta.useValue;
|
ngFactoryDef = getCompilerFacade().compileFactory(
|
||||||
} else if (isUseFactoryProvider(meta)) {
|
angularCoreDiEnv, `ng:///${type.name}/ngFactoryDef.js`, {
|
||||||
// The user explicitly specified useFactory.
|
name: metadata.name,
|
||||||
compilerMeta.useFactory = meta.useFactory;
|
type: metadata.type,
|
||||||
} else if (isUseExistingProvider(meta)) {
|
typeArgumentCount: metadata.typeArgumentCount,
|
||||||
// The user explicitly specified useExisting.
|
deps: reflectDependencies(type),
|
||||||
compilerMeta.useExisting = meta.useExisting;
|
injectFn: 'inject',
|
||||||
} else {
|
isPipe: false
|
||||||
// Can't happen - either hasAProvider will be false, or one of the providers will be set.
|
});
|
||||||
throw new Error(`Unreachable state.`);
|
|
||||||
}
|
}
|
||||||
def = getCompilerFacade().compileInjectable(
|
return ngFactoryDef;
|
||||||
angularCoreDiEnv, `ng:///${type.name}/ngInjectableDef.js`, compilerMeta);
|
},
|
||||||
}
|
// Leave this configurable so that the factories from directives or pipes can take precedence.
|
||||||
return def;
|
configurable: true
|
||||||
},
|
});
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type UseClassProvider = Injectable & ClassSansProvider & {deps?: any[]};
|
type UseClassProvider = Injectable & ClassSansProvider & {deps?: any[]};
|
||||||
|
@ -98,3 +86,32 @@ function isUseFactoryProvider(meta: Injectable): meta is Injectable&FactorySansP
|
||||||
function isUseExistingProvider(meta: Injectable): meta is Injectable&ExistingSansProvider {
|
function isUseExistingProvider(meta: Injectable): meta is Injectable&ExistingSansProvider {
|
||||||
return (meta as ExistingSansProvider).useExisting !== undefined;
|
return (meta as ExistingSansProvider).useExisting !== undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getInjectableMetadata(type: Type<any>, srcMeta?: Injectable): R3InjectableMetadataFacade {
|
||||||
|
// Allow the compilation of a class with a `@Injectable()` decorator without parameters
|
||||||
|
const meta: Injectable = srcMeta || {providedIn: null};
|
||||||
|
const compilerMeta: R3InjectableMetadataFacade = {
|
||||||
|
name: type.name,
|
||||||
|
type: type,
|
||||||
|
typeArgumentCount: 0,
|
||||||
|
providedIn: meta.providedIn,
|
||||||
|
userDeps: undefined,
|
||||||
|
};
|
||||||
|
if ((isUseClassProvider(meta) || isUseFactoryProvider(meta)) && meta.deps !== undefined) {
|
||||||
|
compilerMeta.userDeps = convertDependencies(meta.deps);
|
||||||
|
}
|
||||||
|
if (isUseClassProvider(meta)) {
|
||||||
|
// The user explicitly specified useClass, and may or may not have provided deps.
|
||||||
|
compilerMeta.useClass = resolveForwardRef(meta.useClass);
|
||||||
|
} else if (isUseValueProvider(meta)) {
|
||||||
|
// The user explicitly specified useValue.
|
||||||
|
compilerMeta.useValue = resolveForwardRef(meta.useValue);
|
||||||
|
} else if (isUseFactoryProvider(meta)) {
|
||||||
|
// The user explicitly specified useFactory.
|
||||||
|
compilerMeta.useFactory = meta.useFactory;
|
||||||
|
} else if (isUseExistingProvider(meta)) {
|
||||||
|
// The user explicitly specified useExisting.
|
||||||
|
compilerMeta.useExisting = resolveForwardRef(meta.useExisting);
|
||||||
|
}
|
||||||
|
return compilerMeta;
|
||||||
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ export {ɵɵinject} from './di/injector_compatibility';
|
||||||
export {ɵɵInjectableDef, ɵɵInjectorDef, ɵɵdefineInjectable, ɵɵdefineInjector} from './di/interface/defs';
|
export {ɵɵInjectableDef, ɵɵInjectorDef, ɵɵdefineInjectable, ɵɵdefineInjector} from './di/interface/defs';
|
||||||
export {NgModuleDef, ɵɵNgModuleDefWithMeta} from './metadata/ng_module';
|
export {NgModuleDef, ɵɵNgModuleDefWithMeta} from './metadata/ng_module';
|
||||||
export {ɵɵdefineNgModule} from './render3/definition';
|
export {ɵɵdefineNgModule} from './render3/definition';
|
||||||
|
export {ɵɵFactoryDef} from './render3/interfaces/definition';
|
||||||
export {setClassMetadata} from './render3/metadata';
|
export {setClassMetadata} from './render3/metadata';
|
||||||
export {NgModuleFactory} from './render3/ng_module_ref';
|
export {NgModuleFactory} from './render3/ng_module_ref';
|
||||||
|
|
||||||
|
|
|
@ -753,11 +753,11 @@ export function getBaseDef<T>(type: any): ɵɵBaseDef<T>|null {
|
||||||
export function getFactoryDef<T>(type: any, throwNotFound: true): FactoryFn<T>;
|
export function getFactoryDef<T>(type: any, throwNotFound: true): FactoryFn<T>;
|
||||||
export function getFactoryDef<T>(type: any): FactoryFn<T>|null;
|
export function getFactoryDef<T>(type: any): FactoryFn<T>|null;
|
||||||
export function getFactoryDef<T>(type: any, throwNotFound?: boolean): FactoryFn<T>|null {
|
export function getFactoryDef<T>(type: any, throwNotFound?: boolean): FactoryFn<T>|null {
|
||||||
const factoryFn = type[NG_FACTORY_DEF] || null;
|
const hasFactoryDef = type.hasOwnProperty(NG_FACTORY_DEF);
|
||||||
if (!factoryFn && throwNotFound === true && ngDevMode) {
|
if (!hasFactoryDef && throwNotFound === true && ngDevMode) {
|
||||||
throw new Error(`Type ${stringify(type)} does not have 'ngFactoryDef' property.`);
|
throw new Error(`Type ${stringify(type)} does not have 'ngFactoryDef' property.`);
|
||||||
}
|
}
|
||||||
return factoryFn;
|
return hasFactoryDef ? type[NG_FACTORY_DEF] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getNgModuleDef<T>(type: any, throwNotFound: true): NgModuleDef<T>;
|
export function getNgModuleDef<T>(type: any, throwNotFound: true): NgModuleDef<T>;
|
||||||
|
|
|
@ -16,7 +16,7 @@ import {Type} from '../interface/type';
|
||||||
import {assertDefined, assertEqual} from '../util/assert';
|
import {assertDefined, assertEqual} from '../util/assert';
|
||||||
|
|
||||||
import {getFactoryDef} from './definition';
|
import {getFactoryDef} from './definition';
|
||||||
import {NG_ELEMENT_ID} from './fields';
|
import {NG_ELEMENT_ID, NG_FACTORY_DEF} from './fields';
|
||||||
import {DirectiveDef, FactoryFn} from './interfaces/definition';
|
import {DirectiveDef, FactoryFn} from './interfaces/definition';
|
||||||
import {NO_PARENT_INJECTOR, NodeInjectorFactory, PARENT_INJECTOR, RelativeInjectorLocation, RelativeInjectorLocationFlags, TNODE, isFactory} from './interfaces/injector';
|
import {NO_PARENT_INJECTOR, NodeInjectorFactory, PARENT_INJECTOR, RelativeInjectorLocation, RelativeInjectorLocationFlags, TNODE, isFactory} from './interfaces/injector';
|
||||||
import {AttributeMarker, TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType} from './interfaces/node';
|
import {AttributeMarker, TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType} from './interfaces/node';
|
||||||
|
@ -642,9 +642,11 @@ export function ɵɵgetFactoryOf<T>(type: Type<any>): FactoryFn<T>|null {
|
||||||
}) as any;
|
}) as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(crisbeto): unify injectable factories with getFactory.
|
let factory = getFactoryDef<T>(typeAny);
|
||||||
const def = getInjectableDef<T>(typeAny) || getInjectorDef<T>(typeAny);
|
if (factory === null) {
|
||||||
const factory = def && def.factory || getFactoryDef<T>(typeAny);
|
const injectorDef = getInjectorDef<T>(typeAny);
|
||||||
|
factory = injectorDef && injectorDef.factory;
|
||||||
|
}
|
||||||
return factory || null;
|
return factory || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -653,7 +655,7 @@ export function ɵɵgetFactoryOf<T>(type: Type<any>): FactoryFn<T>|null {
|
||||||
*/
|
*/
|
||||||
export function ɵɵgetInheritedFactory<T>(type: Type<any>): (type: Type<T>) => T {
|
export function ɵɵgetInheritedFactory<T>(type: Type<any>): (type: Type<T>) => T {
|
||||||
const proto = Object.getPrototypeOf(type.prototype).constructor as Type<any>;
|
const proto = Object.getPrototypeOf(type.prototype).constructor as Type<any>;
|
||||||
const factory = ɵɵgetFactoryOf<T>(proto);
|
const factory = (proto as any)[NG_FACTORY_DEF] || ɵɵgetFactoryOf<T>(proto);
|
||||||
if (factory !== null) {
|
if (factory !== null) {
|
||||||
return factory;
|
return factory;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -7,9 +7,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {R3DirectiveMetadataFacade, getCompilerFacade} from '../../compiler/compiler_facade';
|
import {R3DirectiveMetadataFacade, getCompilerFacade} from '../../compiler/compiler_facade';
|
||||||
import {CompilerFacade, R3BaseMetadataFacade, R3ComponentMetadataFacade, R3QueryMetadataFacade} from '../../compiler/compiler_facade_interface';
|
import {R3BaseMetadataFacade, R3ComponentMetadataFacade, R3QueryMetadataFacade} from '../../compiler/compiler_facade_interface';
|
||||||
import {resolveForwardRef} from '../../di/forward_ref';
|
import {resolveForwardRef} from '../../di/forward_ref';
|
||||||
import {compileInjectable} from '../../di/jit/injectable';
|
|
||||||
import {getReflect, reflectDependencies} from '../../di/jit/util';
|
import {getReflect, reflectDependencies} from '../../di/jit/util';
|
||||||
import {Type} from '../../interface/type';
|
import {Type} from '../../interface/type';
|
||||||
import {Query} from '../../metadata/di';
|
import {Query} from '../../metadata/di';
|
||||||
|
@ -43,31 +42,52 @@ export function compileComponent(type: Type<any>, metadata: Component): void {
|
||||||
(typeof ngDevMode === 'undefined' || ngDevMode) && initNgDevMode();
|
(typeof ngDevMode === 'undefined' || ngDevMode) && initNgDevMode();
|
||||||
|
|
||||||
let ngComponentDef: any = null;
|
let ngComponentDef: any = null;
|
||||||
let ngFactoryDef: any = null;
|
|
||||||
|
|
||||||
// Metadata may have resources which need to be resolved.
|
// Metadata may have resources which need to be resolved.
|
||||||
maybeQueueResolutionOfComponentResources(type, metadata);
|
maybeQueueResolutionOfComponentResources(type, metadata);
|
||||||
|
|
||||||
Object.defineProperty(type, NG_FACTORY_DEF, {
|
// Note that we're using the same function as `Directive`, because that's only subset of metadata
|
||||||
get: () => {
|
// that we need to create the ngFactoryDef. We're avoiding using the component metadata
|
||||||
if (ngFactoryDef === null) {
|
// because we'd have to resolve the asynchronous templates.
|
||||||
const compiler = getCompilerFacade();
|
addDirectiveFactoryDef(type, metadata);
|
||||||
const meta = getComponentMetadata(compiler, type, metadata);
|
|
||||||
ngFactoryDef = compiler.compileFactory(
|
|
||||||
angularCoreEnv, `ng:///${type.name}/ngFactory.js`, meta.metadata);
|
|
||||||
}
|
|
||||||
return ngFactoryDef;
|
|
||||||
},
|
|
||||||
// Make the property configurable in dev mode to allow overriding in tests
|
|
||||||
configurable: !!ngDevMode,
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.defineProperty(type, NG_COMPONENT_DEF, {
|
Object.defineProperty(type, NG_COMPONENT_DEF, {
|
||||||
get: () => {
|
get: () => {
|
||||||
if (ngComponentDef === null) {
|
if (ngComponentDef === null) {
|
||||||
const compiler = getCompilerFacade();
|
const compiler = getCompilerFacade();
|
||||||
const meta = getComponentMetadata(compiler, type, metadata);
|
|
||||||
ngComponentDef = compiler.compileComponent(angularCoreEnv, meta.templateUrl, meta.metadata);
|
if (componentNeedsResolution(metadata)) {
|
||||||
|
const error = [`Component '${type.name}' is not resolved:`];
|
||||||
|
if (metadata.templateUrl) {
|
||||||
|
error.push(` - templateUrl: ${metadata.templateUrl}`);
|
||||||
|
}
|
||||||
|
if (metadata.styleUrls && metadata.styleUrls.length) {
|
||||||
|
error.push(` - styleUrls: ${JSON.stringify(metadata.styleUrls)}`);
|
||||||
|
}
|
||||||
|
error.push(`Did you run and wait for 'resolveComponentResources()'?`);
|
||||||
|
throw new Error(error.join('\n'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const templateUrl = metadata.templateUrl || `ng:///${type.name}/template.html`;
|
||||||
|
const meta: R3ComponentMetadataFacade = {
|
||||||
|
...directiveMetadata(type, metadata),
|
||||||
|
typeSourceSpan: compiler.createParseSourceSpan('Component', type.name, templateUrl),
|
||||||
|
template: metadata.template || '',
|
||||||
|
preserveWhitespaces: metadata.preserveWhitespaces || false,
|
||||||
|
styles: metadata.styles || EMPTY_ARRAY,
|
||||||
|
animations: metadata.animations,
|
||||||
|
directives: [],
|
||||||
|
changeDetection: metadata.changeDetection,
|
||||||
|
pipes: new Map(),
|
||||||
|
encapsulation: metadata.encapsulation || ViewEncapsulation.Emulated,
|
||||||
|
interpolation: metadata.interpolation,
|
||||||
|
viewProviders: metadata.viewProviders || null,
|
||||||
|
};
|
||||||
|
if (meta.usesInheritance) {
|
||||||
|
addBaseDefToUndecoratedParents(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngComponentDef = compiler.compileComponent(angularCoreEnv, templateUrl, meta);
|
||||||
|
|
||||||
// When NgModule decorator executed, we enqueued the module definition such that
|
// When NgModule decorator executed, we enqueued the module definition such that
|
||||||
// it would only dequeue and add itself as module scope to all of its declarations,
|
// it would only dequeue and add itself as module scope to all of its declarations,
|
||||||
|
@ -90,45 +110,6 @@ export function compileComponent(type: Type<any>, metadata: Component): void {
|
||||||
// Make the property configurable in dev mode to allow overriding in tests
|
// Make the property configurable in dev mode to allow overriding in tests
|
||||||
configurable: !!ngDevMode,
|
configurable: !!ngDevMode,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add ngInjectableDef so components are reachable through the module injector by default
|
|
||||||
// This is mostly to support injecting components in tests. In real application code,
|
|
||||||
// components should be retrieved through the node injector, so this isn't a problem.
|
|
||||||
compileInjectable(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getComponentMetadata(compiler: CompilerFacade, type: Type<any>, metadata: Component) {
|
|
||||||
if (componentNeedsResolution(metadata)) {
|
|
||||||
const error = [`Component '${type.name}' is not resolved:`];
|
|
||||||
if (metadata.templateUrl) {
|
|
||||||
error.push(` - templateUrl: ${metadata.templateUrl}`);
|
|
||||||
}
|
|
||||||
if (metadata.styleUrls && metadata.styleUrls.length) {
|
|
||||||
error.push(` - styleUrls: ${JSON.stringify(metadata.styleUrls)}`);
|
|
||||||
}
|
|
||||||
error.push(`Did you run and wait for 'resolveComponentResources()'?`);
|
|
||||||
throw new Error(error.join('\n'));
|
|
||||||
}
|
|
||||||
|
|
||||||
const templateUrl = metadata.templateUrl || `ng:///${type.name}/template.html`;
|
|
||||||
const meta: R3ComponentMetadataFacade = {
|
|
||||||
...directiveMetadata(type, metadata),
|
|
||||||
typeSourceSpan: compiler.createParseSourceSpan('Component', type.name, templateUrl),
|
|
||||||
template: metadata.template || '',
|
|
||||||
preserveWhitespaces: metadata.preserveWhitespaces || false,
|
|
||||||
styles: metadata.styles || EMPTY_ARRAY,
|
|
||||||
animations: metadata.animations,
|
|
||||||
directives: [],
|
|
||||||
changeDetection: metadata.changeDetection,
|
|
||||||
pipes: new Map(),
|
|
||||||
encapsulation: metadata.encapsulation || ViewEncapsulation.Emulated,
|
|
||||||
interpolation: metadata.interpolation,
|
|
||||||
viewProviders: metadata.viewProviders || null,
|
|
||||||
};
|
|
||||||
if (meta.usesInheritance) {
|
|
||||||
addBaseDefToUndecoratedParents(type);
|
|
||||||
}
|
|
||||||
return {metadata: meta, templateUrl};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasSelectorScope<T>(component: Type<T>): component is Type<T>&
|
function hasSelectorScope<T>(component: Type<T>): component is Type<T>&
|
||||||
|
@ -145,23 +126,8 @@ function hasSelectorScope<T>(component: Type<T>): component is Type<T>&
|
||||||
*/
|
*/
|
||||||
export function compileDirective(type: Type<any>, directive: Directive | null): void {
|
export function compileDirective(type: Type<any>, directive: Directive | null): void {
|
||||||
let ngDirectiveDef: any = null;
|
let ngDirectiveDef: any = null;
|
||||||
let ngFactoryDef: any = null;
|
|
||||||
|
|
||||||
Object.defineProperty(type, NG_FACTORY_DEF, {
|
addDirectiveFactoryDef(type, directive || {});
|
||||||
get: () => {
|
|
||||||
if (ngFactoryDef === null) {
|
|
||||||
// `directive` can be null in the case of abstract directives as a base class
|
|
||||||
// that use `@Directive()` with no selector. In that case, pass empty object to the
|
|
||||||
// `directiveMetadata` function instead of null.
|
|
||||||
const meta = getDirectiveMetadata(type, directive || {});
|
|
||||||
ngFactoryDef = getCompilerFacade().compileFactory(
|
|
||||||
angularCoreEnv, `ng:///${type.name}/ngFactory.js`, meta.metadata);
|
|
||||||
}
|
|
||||||
return ngFactoryDef;
|
|
||||||
},
|
|
||||||
// Make the property configurable in dev mode to allow overriding in tests
|
|
||||||
configurable: !!ngDevMode,
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.defineProperty(type, NG_DIRECTIVE_DEF, {
|
Object.defineProperty(type, NG_DIRECTIVE_DEF, {
|
||||||
get: () => {
|
get: () => {
|
||||||
|
@ -178,11 +144,6 @@ export function compileDirective(type: Type<any>, directive: Directive | null):
|
||||||
// Make the property configurable in dev mode to allow overriding in tests
|
// Make the property configurable in dev mode to allow overriding in tests
|
||||||
configurable: !!ngDevMode,
|
configurable: !!ngDevMode,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add ngInjectableDef so directives are reachable through the module injector by default
|
|
||||||
// This is mostly to support injecting directives in tests. In real application code,
|
|
||||||
// directives should be retrieved through the node injector, so this isn't a problem.
|
|
||||||
compileInjectable(type);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDirectiveMetadata(type: Type<any>, metadata: Directive) {
|
function getDirectiveMetadata(type: Type<any>, metadata: Directive) {
|
||||||
|
@ -197,6 +158,24 @@ function getDirectiveMetadata(type: Type<any>, metadata: Directive) {
|
||||||
return {metadata: facade, sourceMapUrl};
|
return {metadata: facade, sourceMapUrl};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addDirectiveFactoryDef(type: Type<any>, metadata: Directive | Component) {
|
||||||
|
let ngFactoryDef: any = null;
|
||||||
|
|
||||||
|
Object.defineProperty(type, NG_FACTORY_DEF, {
|
||||||
|
get: () => {
|
||||||
|
if (ngFactoryDef === null) {
|
||||||
|
const meta = getDirectiveMetadata(type, metadata);
|
||||||
|
ngFactoryDef = getCompilerFacade().compileFactory(
|
||||||
|
angularCoreEnv, `ng:///${type.name}/ngFactoryDef.js`,
|
||||||
|
{...meta.metadata, injectFn: 'directiveInject', isPipe: false});
|
||||||
|
}
|
||||||
|
return ngFactoryDef;
|
||||||
|
},
|
||||||
|
// Make the property configurable in dev mode to allow overriding in tests
|
||||||
|
configurable: !!ngDevMode,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function extendsDirectlyFromObject(type: Type<any>): boolean {
|
export function extendsDirectlyFromObject(type: Type<any>): boolean {
|
||||||
return Object.getPrototypeOf(type.prototype) === Object.prototype;
|
return Object.getPrototypeOf(type.prototype) === Object.prototype;
|
||||||
}
|
}
|
||||||
|
@ -225,7 +204,7 @@ export function directiveMetadata(type: Type<any>, metadata: Directive): R3Direc
|
||||||
usesInheritance: !extendsDirectlyFromObject(type),
|
usesInheritance: !extendsDirectlyFromObject(type),
|
||||||
exportAs: extractExportAs(metadata.exportAs),
|
exportAs: extractExportAs(metadata.exportAs),
|
||||||
providers: metadata.providers || null,
|
providers: metadata.providers || null,
|
||||||
viewQueries: extractQueriesMetadata(type, propMetadata, isViewQuery),
|
viewQueries: extractQueriesMetadata(type, propMetadata, isViewQuery)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {getCompilerFacade} from '../../compiler/compiler_facade';
|
import {R3PipeMetadataFacade, getCompilerFacade} from '../../compiler/compiler_facade';
|
||||||
import {reflectDependencies} from '../../di/jit/util';
|
import {reflectDependencies} from '../../di/jit/util';
|
||||||
import {Type} from '../../interface/type';
|
import {Type} from '../../interface/type';
|
||||||
import {Pipe} from '../../metadata/directives';
|
import {Pipe} from '../../metadata/directives';
|
||||||
|
@ -23,7 +23,8 @@ export function compilePipe(type: Type<any>, meta: Pipe): void {
|
||||||
if (ngFactoryDef === null) {
|
if (ngFactoryDef === null) {
|
||||||
const metadata = getPipeMetadata(type, meta);
|
const metadata = getPipeMetadata(type, meta);
|
||||||
ngFactoryDef = getCompilerFacade().compileFactory(
|
ngFactoryDef = getCompilerFacade().compileFactory(
|
||||||
angularCoreEnv, `ng:///${metadata.name}/ngFactory.js`, metadata, true);
|
angularCoreEnv, `ng:///${metadata.name}/ngFactoryDef.js`,
|
||||||
|
{...metadata, injectFn: 'directiveInject', isPipe: true});
|
||||||
}
|
}
|
||||||
return ngFactoryDef;
|
return ngFactoryDef;
|
||||||
},
|
},
|
||||||
|
@ -45,7 +46,7 @@ export function compilePipe(type: Type<any>, meta: Pipe): void {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPipeMetadata(type: Type<any>, meta: Pipe) {
|
function getPipeMetadata(type: Type<any>, meta: Pipe): R3PipeMetadataFacade {
|
||||||
return {
|
return {
|
||||||
type: type,
|
type: type,
|
||||||
typeArgumentCount: 0,
|
typeArgumentCount: 0,
|
||||||
|
|
|
@ -951,6 +951,37 @@ describe('di', () => {
|
||||||
`This will become an error in v10. Please add @Injectable() to the "SubSubClass" class.`);
|
`This will become an error in v10. Please add @Injectable() to the "SubSubClass" class.`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should instantiate correct class when undecorated class extends an injectable', () => {
|
||||||
|
@Injectable()
|
||||||
|
class MyService {
|
||||||
|
id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
class MyRootService extends MyService {
|
||||||
|
id = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({template: ''})
|
||||||
|
class App {
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({declarations: [App], providers: [MyRootService]});
|
||||||
|
const warnSpy = spyOn(console, 'warn');
|
||||||
|
const fixture = TestBed.createComponent(App);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const provider = TestBed.inject(MyRootService);
|
||||||
|
|
||||||
|
expect(provider instanceof MyRootService).toBe(true);
|
||||||
|
expect(provider.id).toBe(2);
|
||||||
|
|
||||||
|
if (ivyEnabled) {
|
||||||
|
expect(warnSpy).toHaveBeenCalledWith(
|
||||||
|
`DEPRECATED: DI is instantiating a token "MyRootService" that inherits its @Injectable decorator but does not provide one itself.\n` +
|
||||||
|
`This will become an error in v10. Please add @Injectable() to the "MyRootService" class.`);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('inject', () => {
|
describe('inject', () => {
|
||||||
|
|
Loading…
Reference in New Issue