angular-cn/packages/compiler/src/metadata_resolver.ts

1234 lines
46 KiB
TypeScript
Raw Normal View History

/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {StaticSymbol, StaticSymbolCache} from './aot/static_symbol';
import {ngfactoryFilePath} from './aot/util';
import {assertArrayOfStrings, assertInterpolationSymbols} from './assertions';
import * as cpl from './compile_metadata';
import {CompileReflector} from './compile_reflector';
import {CompilerConfig} from './config';
import {ChangeDetectionStrategy, Component, Directive, Injectable, ModuleWithProviders, Provider, Query, SchemaMetadata, Type, ViewEncapsulation, createAttribute, createComponent, createHost, createInject, createInjectable, createInjectionToken, createNgModule, createOptional, createSelf, createSkipSelf} from './core';
import {DirectiveNormalizer} from './directive_normalizer';
import {DirectiveResolver, findLast} from './directive_resolver';
import {Identifiers} from './identifiers';
import {getAllLifecycleHooks} from './lifecycle_reflector';
import {HtmlParser} from './ml_parser/html_parser';
import {NgModuleResolver} from './ng_module_resolver';
import {PipeResolver} from './pipe_resolver';
import {ElementSchemaRegistry} from './schema/element_schema_registry';
import {CssSelector} from './selector';
import {SummaryResolver} from './summary_resolver';
import {Console, SyncAsync, ValueTransformer, isPromise, noUndefined, resolveForwardRef, stringify, syntaxError, visitValue} from './util';
export type ErrorCollector = (error: any, type?: any) => void;
export const ERROR_COMPONENT_TYPE = 'ngComponentType';
// Design notes:
// - don't lazily create metadata:
// For some metadata, we need to do async work sometimes,
// so the user has to kick off this loading.
// But we want to report errors even when the async work is
// not required to check that the user would have been able
// to wait correctly.
export class CompileMetadataResolver {
private _nonNormalizedDirectiveCache =
new Map<Type, {annotation: Directive, metadata: cpl.CompileDirectiveMetadata}>();
private _directiveCache = new Map<Type, cpl.CompileDirectiveMetadata>();
private _summaryCache = new Map<Type, cpl.CompileTypeSummary|null>();
private _pipeCache = new Map<Type, cpl.CompilePipeMetadata>();
private _ngModuleCache = new Map<Type, cpl.CompileNgModuleMetadata>();
private _ngModuleOfTypes = new Map<Type, Type>();
private _shallowModuleCache = new Map<Type, cpl.CompileShallowModuleMetadata>();
constructor(
private _config: CompilerConfig, private _htmlParser: HtmlParser,
private _ngModuleResolver: NgModuleResolver, private _directiveResolver: DirectiveResolver,
private _pipeResolver: PipeResolver, private _summaryResolver: SummaryResolver<any>,
private _schemaRegistry: ElementSchemaRegistry,
private _directiveNormalizer: DirectiveNormalizer, private _console: Console,
private _staticSymbolCache: StaticSymbolCache, private _reflector: CompileReflector,
private _errorCollector?: ErrorCollector) {}
getReflector(): CompileReflector { return this._reflector; }
clearCacheFor(type: Type) {
const dirMeta = this._directiveCache.get(type);
this._directiveCache.delete(type);
this._nonNormalizedDirectiveCache.delete(type);
this._summaryCache.delete(type);
this._pipeCache.delete(type);
this._ngModuleOfTypes.delete(type);
// Clear all of the NgModule as they contain transitive information!
this._ngModuleCache.clear();
if (dirMeta) {
this._directiveNormalizer.clearCacheFor(dirMeta);
}
}
clearCache(): void {
this._directiveCache.clear();
this._nonNormalizedDirectiveCache.clear();
this._summaryCache.clear();
this._pipeCache.clear();
this._ngModuleCache.clear();
this._ngModuleOfTypes.clear();
this._directiveNormalizer.clearCache();
}
private _createProxyClass(baseType: any, name: string): cpl.ProxyClass {
let delegate: any = null;
const proxyClass: cpl.ProxyClass = <any>function() {
if (!delegate) {
throw new Error(
`Illegal state: Class ${name} for type ${stringify(baseType)} is not compiled yet!`);
}
return delegate.apply(this, arguments);
};
proxyClass.setDelegate = (d) => {
delegate = d;
(<any>proxyClass).prototype = d.prototype;
};
// Make stringify work correctly
(<any>proxyClass).overriddenName = name;
return proxyClass;
}
private getGeneratedClass(dirType: any, name: string): StaticSymbol|cpl.ProxyClass {
if (dirType instanceof StaticSymbol) {
return this._staticSymbolCache.get(ngfactoryFilePath(dirType.filePath), name);
} else {
return this._createProxyClass(dirType, name);
}
}
private getComponentViewClass(dirType: any): StaticSymbol|cpl.ProxyClass {
return this.getGeneratedClass(dirType, cpl.viewClassName(dirType, 0));
}
getHostComponentViewClass(dirType: any): StaticSymbol|cpl.ProxyClass {
return this.getGeneratedClass(dirType, cpl.hostViewClassName(dirType));
}
getHostComponentType(dirType: any): StaticSymbol|cpl.ProxyClass {
const name = `${cpl.identifierName({reference: dirType})}_Host`;
if (dirType instanceof StaticSymbol) {
return this._staticSymbolCache.get(dirType.filePath, name);
}
return this._createProxyClass(dirType, name);
}
private getRendererType(dirType: any): StaticSymbol|object {
if (dirType instanceof StaticSymbol) {
return this._staticSymbolCache.get(
ngfactoryFilePath(dirType.filePath), cpl.rendererTypeName(dirType));
} else {
// returning an object as proxy,
// that we fill later during runtime compilation.
return <any>{};
}
}
private getComponentFactory(
selector: string, dirType: any, inputs: {[key: string]: string}|null,
outputs: {[key: string]: string}): StaticSymbol|object {
if (dirType instanceof StaticSymbol) {
return this._staticSymbolCache.get(
ngfactoryFilePath(dirType.filePath), cpl.componentFactoryName(dirType));
} else {
const hostView = this.getHostComponentViewClass(dirType);
// Note: ngContentSelectors will be filled later once the template is
// loaded.
const createComponentFactory =
this._reflector.resolveExternalReference(Identifiers.createComponentFactory);
return createComponentFactory(selector, dirType, <any>hostView, inputs, outputs, []);
}
}
private initComponentFactory(factory: StaticSymbol|object, ngContentSelectors: string[]) {
if (!(factory instanceof StaticSymbol)) {
(factory as any).ngContentSelectors.push(...ngContentSelectors);
}
}
private _loadSummary(type: any, kind: cpl.CompileSummaryKind): cpl.CompileTypeSummary|null {
let typeSummary = this._summaryCache.get(type);
if (!typeSummary) {
const summary = this._summaryResolver.resolveSummary(type);
typeSummary = summary ? summary.type : null;
this._summaryCache.set(type, typeSummary || null);
}
return typeSummary && typeSummary.summaryKind === kind ? typeSummary : null;
}
getHostComponentMetadata(
compMeta: cpl.CompileDirectiveMetadata,
hostViewType?: StaticSymbol|cpl.ProxyClass): cpl.CompileDirectiveMetadata {
const hostType = this.getHostComponentType(compMeta.type.reference);
if (!hostViewType) {
hostViewType = this.getHostComponentViewClass(hostType);
}
// Note: ! is ok here as this method should only be called with normalized directive
// metadata, which always fills in the selector.
const template = CssSelector.parse(compMeta.selector !)[0].getMatchingElementTemplate();
const templateUrl = '';
const htmlAst = this._htmlParser.parse(template, templateUrl);
return cpl.CompileDirectiveMetadata.create({
isHost: true,
type: {reference: hostType, diDeps: [], lifecycleHooks: []},
template: new cpl.CompileTemplateMetadata({
encapsulation: ViewEncapsulation.None,
template,
templateUrl,
htmlAst,
styles: [],
styleUrls: [],
ngContentSelectors: [],
animations: [],
isInline: true,
externalStylesheets: [],
interpolation: null,
preserveWhitespaces: false,
}),
exportAs: null,
changeDetection: ChangeDetectionStrategy.Default,
inputs: [],
outputs: [],
host: {},
isComponent: true,
selector: '*',
providers: [],
viewProviders: [],
queries: [],
guards: {},
viewQueries: [],
componentViewType: hostViewType,
rendererType:
{id: '__Host__', encapsulation: ViewEncapsulation.None, styles: [], data: {}} as object,
entryComponents: [],
componentFactory: null
});
}
loadDirectiveMetadata(ngModuleType: any, directiveType: any, isSync: boolean): SyncAsync<null> {
if (this._directiveCache.has(directiveType)) {
return null;
}
directiveType = resolveForwardRef(directiveType);
const {annotation, metadata} = this.getNonNormalizedDirectiveMetadata(directiveType) !;
const createDirectiveMetadata = (templateMetadata: cpl.CompileTemplateMetadata | null) => {
const normalizedDirMeta = new cpl.CompileDirectiveMetadata({
isHost: false,
type: metadata.type,
isComponent: metadata.isComponent,
selector: metadata.selector,
exportAs: metadata.exportAs,
changeDetection: metadata.changeDetection,
inputs: metadata.inputs,
outputs: metadata.outputs,
hostListeners: metadata.hostListeners,
hostProperties: metadata.hostProperties,
hostAttributes: metadata.hostAttributes,
providers: metadata.providers,
viewProviders: metadata.viewProviders,
queries: metadata.queries,
guards: metadata.guards,
viewQueries: metadata.viewQueries,
entryComponents: metadata.entryComponents,
componentViewType: metadata.componentViewType,
rendererType: metadata.rendererType,
componentFactory: metadata.componentFactory,
template: templateMetadata
});
if (templateMetadata) {
this.initComponentFactory(metadata.componentFactory !, templateMetadata.ngContentSelectors);
}
this._directiveCache.set(directiveType, normalizedDirMeta);
this._summaryCache.set(directiveType, normalizedDirMeta.toSummary());
return null;
};
if (metadata.isComponent) {
const template = metadata.template !;
const templateMeta = this._directiveNormalizer.normalizeTemplate({
ngModuleType,
componentType: directiveType,
moduleUrl: this._reflector.componentModuleUrl(directiveType, annotation),
encapsulation: template.encapsulation,
template: template.template,
templateUrl: template.templateUrl,
styles: template.styles,
styleUrls: template.styleUrls,
animations: template.animations,
interpolation: template.interpolation,
preserveWhitespaces: template.preserveWhitespaces
});
if (isPromise(templateMeta) && isSync) {
this._reportError(componentStillLoadingError(directiveType), directiveType);
return null;
}
return SyncAsync.then(templateMeta, createDirectiveMetadata);
} else {
// directive
createDirectiveMetadata(null);
return null;
}
}
getNonNormalizedDirectiveMetadata(directiveType: any):
{annotation: Directive, metadata: cpl.CompileDirectiveMetadata}|null {
directiveType = resolveForwardRef(directiveType);
if (!directiveType) {
return null;
}
let cacheEntry = this._nonNormalizedDirectiveCache.get(directiveType);
if (cacheEntry) {
return cacheEntry;
}
const dirMeta = this._directiveResolver.resolve(directiveType, false);
if (!dirMeta) {
return null;
}
let nonNormalizedTemplateMetadata: cpl.CompileTemplateMetadata = undefined !;
if (createComponent.isTypeOf(dirMeta)) {
// component
const compMeta = dirMeta as Component;
assertArrayOfStrings('styles', compMeta.styles);
assertArrayOfStrings('styleUrls', compMeta.styleUrls);
assertInterpolationSymbols('interpolation', compMeta.interpolation);
const animations = compMeta.animations;
nonNormalizedTemplateMetadata = new cpl.CompileTemplateMetadata({
encapsulation: noUndefined(compMeta.encapsulation),
template: noUndefined(compMeta.template),
templateUrl: noUndefined(compMeta.templateUrl),
htmlAst: null,
styles: compMeta.styles || [],
styleUrls: compMeta.styleUrls || [],
animations: animations || [],
interpolation: noUndefined(compMeta.interpolation),
isInline: !!compMeta.template,
externalStylesheets: [],
ngContentSelectors: [],
preserveWhitespaces: noUndefined(dirMeta.preserveWhitespaces),
});
}
let changeDetectionStrategy: ChangeDetectionStrategy = null !;
let viewProviders: cpl.CompileProviderMetadata[] = [];
let entryComponentMetadata: cpl.CompileEntryComponentMetadata[] = [];
let selector = dirMeta.selector;
if (createComponent.isTypeOf(dirMeta)) {
// Component
const compMeta = dirMeta as Component;
changeDetectionStrategy = compMeta.changeDetection !;
if (compMeta.viewProviders) {
viewProviders = this._getProvidersMetadata(
compMeta.viewProviders, entryComponentMetadata,
`viewProviders for "${stringifyType(directiveType)}"`, [], directiveType);
}
if (compMeta.entryComponents) {
entryComponentMetadata = flattenAndDedupeArray(compMeta.entryComponents)
.map((type) => this._getEntryComponentMetadata(type) !)
.concat(entryComponentMetadata);
}
if (!selector) {
selector = this._schemaRegistry.getDefaultComponentElementName();
}
} else {
// Directive
if (!selector) {
this._reportError(
syntaxError(
`Directive ${stringifyType(directiveType)} has no selector, please add it!`),
directiveType);
selector = 'error';
}
}
let providers: cpl.CompileProviderMetadata[] = [];
if (dirMeta.providers != null) {
providers = this._getProvidersMetadata(
dirMeta.providers, entryComponentMetadata,
`providers for "${stringifyType(directiveType)}"`, [], directiveType);
}
let queries: cpl.CompileQueryMetadata[] = [];
let viewQueries: cpl.CompileQueryMetadata[] = [];
if (dirMeta.queries != null) {
queries = this._getQueriesMetadata(dirMeta.queries, false, directiveType);
viewQueries = this._getQueriesMetadata(dirMeta.queries, true, directiveType);
}
const metadata = cpl.CompileDirectiveMetadata.create({
isHost: false,
selector: selector,
exportAs: noUndefined(dirMeta.exportAs),
isComponent: !!nonNormalizedTemplateMetadata,
type: this._getTypeMetadata(directiveType),
template: nonNormalizedTemplateMetadata,
changeDetection: changeDetectionStrategy,
inputs: dirMeta.inputs || [],
outputs: dirMeta.outputs || [],
host: dirMeta.host || {},
providers: providers || [],
viewProviders: viewProviders || [],
queries: queries || [],
guards: dirMeta.guards || {},
viewQueries: viewQueries || [],
entryComponents: entryComponentMetadata,
componentViewType: nonNormalizedTemplateMetadata ? this.getComponentViewClass(directiveType) :
null,
rendererType: nonNormalizedTemplateMetadata ? this.getRendererType(directiveType) : null,
componentFactory: null
});
if (nonNormalizedTemplateMetadata) {
metadata.componentFactory =
this.getComponentFactory(selector, directiveType, metadata.inputs, metadata.outputs);
}
cacheEntry = {metadata, annotation: dirMeta};
this._nonNormalizedDirectiveCache.set(directiveType, cacheEntry);
return cacheEntry;
}
/**
* Gets the metadata for the given directive.
* This assumes `loadNgModuleDirectiveAndPipeMetadata` has been called first.
*/
getDirectiveMetadata(directiveType: any): cpl.CompileDirectiveMetadata {
const dirMeta = this._directiveCache.get(directiveType) !;
if (!dirMeta) {
this._reportError(
syntaxError(
`Illegal state: getDirectiveMetadata can only be called after loadNgModuleDirectiveAndPipeMetadata for a module that declares it. Directive ${stringifyType(directiveType)}.`),
directiveType);
}
return dirMeta;
}
getDirectiveSummary(dirType: any): cpl.CompileDirectiveSummary {
const dirSummary =
<cpl.CompileDirectiveSummary>this._loadSummary(dirType, cpl.CompileSummaryKind.Directive);
if (!dirSummary) {
this._reportError(
syntaxError(
`Illegal state: Could not load the summary for directive ${stringifyType(dirType)}.`),
dirType);
}
return dirSummary;
}
isDirective(type: any) {
return !!this._loadSummary(type, cpl.CompileSummaryKind.Directive) ||
this._directiveResolver.isDirective(type);
}
isPipe(type: any) {
return !!this._loadSummary(type, cpl.CompileSummaryKind.Pipe) ||
this._pipeResolver.isPipe(type);
}
isNgModule(type: any) {
return !!this._loadSummary(type, cpl.CompileSummaryKind.NgModule) ||
this._ngModuleResolver.isNgModule(type);
}
getNgModuleSummary(moduleType: any, alreadyCollecting: Set<any>|null = null):
cpl.CompileNgModuleSummary|null {
let moduleSummary: cpl.CompileNgModuleSummary|null =
<cpl.CompileNgModuleSummary>this._loadSummary(moduleType, cpl.CompileSummaryKind.NgModule);
if (!moduleSummary) {
const moduleMeta = this.getNgModuleMetadata(moduleType, false, alreadyCollecting);
moduleSummary = moduleMeta ? moduleMeta.toSummary() : null;
if (moduleSummary) {
this._summaryCache.set(moduleType, moduleSummary);
}
}
return moduleSummary;
}
/**
* Loads the declared directives and pipes of an NgModule.
*/
loadNgModuleDirectiveAndPipeMetadata(moduleType: any, isSync: boolean, throwIfNotFound = true):
Promise<any> {
const ngModule = this.getNgModuleMetadata(moduleType, throwIfNotFound);
const loading: Promise<any>[] = [];
if (ngModule) {
ngModule.declaredDirectives.forEach((id) => {
const promise = this.loadDirectiveMetadata(moduleType, id.reference, isSync);
if (promise) {
loading.push(promise);
}
});
ngModule.declaredPipes.forEach((id) => this._loadPipeMetadata(id.reference));
}
return Promise.all(loading);
}
getShallowModuleMetadata(moduleType: any): cpl.CompileShallowModuleMetadata|null {
let compileMeta = this._shallowModuleCache.get(moduleType);
if (compileMeta) {
return compileMeta;
}
const ngModuleMeta =
findLast(this._reflector.shallowAnnotations(moduleType), createNgModule.isTypeOf);
compileMeta = {
type: this._getTypeMetadata(moduleType),
rawExports: ngModuleMeta.exports,
rawImports: ngModuleMeta.imports,
rawProviders: ngModuleMeta.providers,
};
this._shallowModuleCache.set(moduleType, compileMeta);
return compileMeta;
}
getNgModuleMetadata(
moduleType: any, throwIfNotFound = true,
alreadyCollecting: Set<any>|null = null): cpl.CompileNgModuleMetadata|null {
moduleType = resolveForwardRef(moduleType);
2016-09-14 18:58:18 -04:00
let compileMeta = this._ngModuleCache.get(moduleType);
if (compileMeta) {
return compileMeta;
}
const meta = this._ngModuleResolver.resolve(moduleType, throwIfNotFound);
if (!meta) {
return null;
}
const declaredDirectives: cpl.CompileIdentifierMetadata[] = [];
const exportedNonModuleIdentifiers: cpl.CompileIdentifierMetadata[] = [];
const declaredPipes: cpl.CompileIdentifierMetadata[] = [];
const importedModules: cpl.CompileNgModuleSummary[] = [];
const exportedModules: cpl.CompileNgModuleSummary[] = [];
const providers: cpl.CompileProviderMetadata[] = [];
const entryComponents: cpl.CompileEntryComponentMetadata[] = [];
const bootstrapComponents: cpl.CompileIdentifierMetadata[] = [];
const schemas: SchemaMetadata[] = [];
if (meta.imports) {
flattenAndDedupeArray(meta.imports).forEach((importedType) => {
let importedModuleType: Type = undefined !;
if (isValidType(importedType)) {
importedModuleType = importedType;
} else if (importedType && importedType.ngModule) {
const moduleWithProviders: ModuleWithProviders = importedType;
importedModuleType = moduleWithProviders.ngModule;
if (moduleWithProviders.providers) {
providers.push(...this._getProvidersMetadata(
moduleWithProviders.providers, entryComponents,
`provider for the NgModule '${stringifyType(importedModuleType)}'`, [],
importedType));
}
}
2016-09-14 18:58:18 -04:00
if (importedModuleType) {
if (this._checkSelfImport(moduleType, importedModuleType)) return;
if (!alreadyCollecting) alreadyCollecting = new Set();
if (alreadyCollecting.has(importedModuleType)) {
this._reportError(
syntaxError(
`${this._getTypeDescriptor(importedModuleType)} '${stringifyType(importedType)}' is imported recursively by the module '${stringifyType(moduleType)}'.`),
moduleType);
return;
}
alreadyCollecting.add(importedModuleType);
const importedModuleSummary =
this.getNgModuleSummary(importedModuleType, alreadyCollecting);
alreadyCollecting.delete(importedModuleType);
if (!importedModuleSummary) {
this._reportError(
syntaxError(
`Unexpected ${this._getTypeDescriptor(importedType)} '${stringifyType(importedType)}' imported by the module '${stringifyType(moduleType)}'. Please add a @NgModule annotation.`),
moduleType);
return;
}
importedModules.push(importedModuleSummary);
} else {
this._reportError(
syntaxError(
`Unexpected value '${stringifyType(importedType)}' imported by the module '${stringifyType(moduleType)}'`),
moduleType);
return;
}
});
}
if (meta.exports) {
flattenAndDedupeArray(meta.exports).forEach((exportedType) => {
if (!isValidType(exportedType)) {
this._reportError(
syntaxError(
`Unexpected value '${stringifyType(exportedType)}' exported by the module '${stringifyType(moduleType)}'`),
moduleType);
return;
}
if (!alreadyCollecting) alreadyCollecting = new Set();
if (alreadyCollecting.has(exportedType)) {
this._reportError(
syntaxError(
`${this._getTypeDescriptor(exportedType)} '${stringify(exportedType)}' is exported recursively by the module '${stringifyType(moduleType)}'`),
moduleType);
return;
}
alreadyCollecting.add(exportedType);
const exportedModuleSummary = this.getNgModuleSummary(exportedType, alreadyCollecting);
alreadyCollecting.delete(exportedType);
if (exportedModuleSummary) {
exportedModules.push(exportedModuleSummary);
} else {
exportedNonModuleIdentifiers.push(this._getIdentifierMetadata(exportedType));
}
});
}
// Note: This will be modified later, so we rely on
// getting a new instance every time!
const transitiveModule = this._getTransitiveNgModuleMetadata(importedModules, exportedModules);
if (meta.declarations) {
flattenAndDedupeArray(meta.declarations).forEach((declaredType) => {
if (!isValidType(declaredType)) {
this._reportError(
syntaxError(
`Unexpected value '${stringifyType(declaredType)}' declared by the module '${stringifyType(moduleType)}'`),
moduleType);
return;
}
const declaredIdentifier = this._getIdentifierMetadata(declaredType);
if (this.isDirective(declaredType)) {
transitiveModule.addDirective(declaredIdentifier);
declaredDirectives.push(declaredIdentifier);
this._addTypeToModule(declaredType, moduleType);
} else if (this.isPipe(declaredType)) {
transitiveModule.addPipe(declaredIdentifier);
transitiveModule.pipes.push(declaredIdentifier);
declaredPipes.push(declaredIdentifier);
this._addTypeToModule(declaredType, moduleType);
} else {
this._reportError(
syntaxError(
`Unexpected ${this._getTypeDescriptor(declaredType)} '${stringifyType(declaredType)}' declared by the module '${stringifyType(moduleType)}'. Please add a @Pipe/@Directive/@Component annotation.`),
moduleType);
return;
}
});
}
const exportedDirectives: cpl.CompileIdentifierMetadata[] = [];
const exportedPipes: cpl.CompileIdentifierMetadata[] = [];
exportedNonModuleIdentifiers.forEach((exportedId) => {
if (transitiveModule.directivesSet.has(exportedId.reference)) {
exportedDirectives.push(exportedId);
transitiveModule.addExportedDirective(exportedId);
} else if (transitiveModule.pipesSet.has(exportedId.reference)) {
exportedPipes.push(exportedId);
transitiveModule.addExportedPipe(exportedId);
} else {
this._reportError(
syntaxError(
`Can't export ${this._getTypeDescriptor(exportedId.reference)} ${stringifyType(exportedId.reference)} from ${stringifyType(moduleType)} as it was neither declared nor imported!`),
moduleType);
return;
}
});
// The providers of the module have to go last
// so that they overwrite any other provider we already added.
if (meta.providers) {
providers.push(...this._getProvidersMetadata(
meta.providers, entryComponents,
`provider for the NgModule '${stringifyType(moduleType)}'`, [], moduleType));
}
2016-09-14 18:58:18 -04:00
if (meta.entryComponents) {
entryComponents.push(...flattenAndDedupeArray(meta.entryComponents)
.map(type => this._getEntryComponentMetadata(type) !));
}
2016-09-14 18:58:18 -04:00
if (meta.bootstrap) {
flattenAndDedupeArray(meta.bootstrap).forEach(type => {
if (!isValidType(type)) {
this._reportError(
syntaxError(
`Unexpected value '${stringifyType(type)}' used in the bootstrap property of module '${stringifyType(moduleType)}'`),
moduleType);
return;
}
bootstrapComponents.push(this._getIdentifierMetadata(type));
});
}
2016-09-14 18:58:18 -04:00
entryComponents.push(
...bootstrapComponents.map(type => this._getEntryComponentMetadata(type.reference) !));
2016-09-14 18:58:18 -04:00
if (meta.schemas) {
schemas.push(...flattenAndDedupeArray(meta.schemas));
}
compileMeta = new cpl.CompileNgModuleMetadata({
type: this._getTypeMetadata(moduleType),
providers,
entryComponents,
bootstrapComponents,
schemas,
declaredDirectives,
exportedDirectives,
declaredPipes,
exportedPipes,
importedModules,
exportedModules,
transitiveModule,
id: meta.id || null,
});
2016-09-14 18:58:18 -04:00
entryComponents.forEach((id) => transitiveModule.addEntryComponent(id));
providers.forEach((provider) => transitiveModule.addProvider(provider, compileMeta !.type));
transitiveModule.addModule(compileMeta.type);
this._ngModuleCache.set(moduleType, compileMeta);
return compileMeta;
}
private _checkSelfImport(moduleType: Type, importedModuleType: Type): boolean {
if (moduleType === importedModuleType) {
this._reportError(
syntaxError(`'${stringifyType(moduleType)}' module can't import itself`), moduleType);
return true;
}
return false;
}
private _getTypeDescriptor(type: Type): string {
if (isValidType(type)) {
if (this.isDirective(type)) {
return 'directive';
}
2016-09-14 18:58:18 -04:00
if (this.isPipe(type)) {
return 'pipe';
}
2016-09-14 18:58:18 -04:00
if (this.isNgModule(type)) {
return 'module';
}
2016-09-14 18:58:18 -04:00
}
if ((type as any).provide) {
return 'provider';
}
2016-09-14 18:58:18 -04:00
return 'value';
}
private _addTypeToModule(type: Type, moduleType: Type) {
const oldModule = this._ngModuleOfTypes.get(type);
if (oldModule && oldModule !== moduleType) {
this._reportError(
syntaxError(
`Type ${stringifyType(type)} is part of the declarations of 2 modules: ${stringifyType(oldModule)} and ${stringifyType(moduleType)}! ` +
`Please consider moving ${stringifyType(type)} to a higher module that imports ${stringifyType(oldModule)} and ${stringifyType(moduleType)}. ` +
`You can also create a new NgModule that exports and includes ${stringifyType(type)} then import that NgModule in ${stringifyType(oldModule)} and ${stringifyType(moduleType)}.`),
moduleType);
return;
}
this._ngModuleOfTypes.set(type, moduleType);
}
private _getTransitiveNgModuleMetadata(
importedModules: cpl.CompileNgModuleSummary[],
exportedModules: cpl.CompileNgModuleSummary[]): cpl.TransitiveCompileNgModuleMetadata {
// collect `providers` / `entryComponents` from all imported and all exported modules
const result = new cpl.TransitiveCompileNgModuleMetadata();
const modulesByToken = new Map<any, Set<any>>();
importedModules.concat(exportedModules).forEach((modSummary) => {
modSummary.modules.forEach((mod) => result.addModule(mod));
modSummary.entryComponents.forEach((comp) => result.addEntryComponent(comp));
const addedTokens = new Set<any>();
modSummary.providers.forEach((entry) => {
const tokenRef = cpl.tokenReference(entry.provider.token);
let prevModules = modulesByToken.get(tokenRef);
if (!prevModules) {
prevModules = new Set<any>();
modulesByToken.set(tokenRef, prevModules);
}
const moduleRef = entry.module.reference;
// Note: the providers of one module may still contain multiple providers
// per token (e.g. for multi providers), and we need to preserve these.
if (addedTokens.has(tokenRef) || !prevModules.has(moduleRef)) {
prevModules.add(moduleRef);
addedTokens.add(tokenRef);
result.addProvider(entry.provider, entry.module);
}
});
});
exportedModules.forEach((modSummary) => {
modSummary.exportedDirectives.forEach((id) => result.addExportedDirective(id));
modSummary.exportedPipes.forEach((id) => result.addExportedPipe(id));
});
importedModules.forEach((modSummary) => {
modSummary.exportedDirectives.forEach((id) => result.addDirective(id));
modSummary.exportedPipes.forEach((id) => result.addPipe(id));
});
return result;
}
private _getIdentifierMetadata(type: Type): cpl.CompileIdentifierMetadata {
type = resolveForwardRef(type);
return {reference: type};
}
isInjectable(type: any): boolean {
const annotations = this._reflector.tryAnnotations(type);
return annotations.some(ann => createInjectable.isTypeOf(ann));
}
getInjectableSummary(type: any): cpl.CompileTypeSummary {
return {
summaryKind: cpl.CompileSummaryKind.Injectable,
type: this._getTypeMetadata(type, null, false)
};
}
getInjectableMetadata(
type: any, dependencies: any[]|null = null,
throwOnUnknownDeps: boolean = true): cpl.CompileInjectableMetadata|null {
const typeSummary = this._loadSummary(type, cpl.CompileSummaryKind.Injectable);
const typeMetadata = typeSummary ?
typeSummary.type :
this._getTypeMetadata(type, dependencies, throwOnUnknownDeps);
const annotations: Injectable[] =
this._reflector.annotations(type).filter(ann => createInjectable.isTypeOf(ann));
if (annotations.length === 0) {
return null;
}
const meta = annotations[annotations.length - 1];
return {
symbol: type,
type: typeMetadata,
providedIn: meta.providedIn,
useValue: meta.useValue,
useClass: meta.useClass,
useExisting: meta.useExisting,
useFactory: meta.useFactory,
deps: meta.deps,
};
}
private _getTypeMetadata(type: Type, dependencies: any[]|null = null, throwOnUnknownDeps = true):
cpl.CompileTypeMetadata {
const identifier = this._getIdentifierMetadata(type);
return {
reference: identifier.reference,
diDeps: this._getDependenciesMetadata(identifier.reference, dependencies, throwOnUnknownDeps),
lifecycleHooks: getAllLifecycleHooks(this._reflector, identifier.reference),
};
}
private _getFactoryMetadata(factory: Function, dependencies: any[]|null = null):
cpl.CompileFactoryMetadata {
factory = resolveForwardRef(factory);
return {reference: factory, diDeps: this._getDependenciesMetadata(factory, dependencies)};
}
/**
* Gets the metadata for the given pipe.
* This assumes `loadNgModuleDirectiveAndPipeMetadata` has been called first.
*/
getPipeMetadata(pipeType: any): cpl.CompilePipeMetadata|null {
const pipeMeta = this._pipeCache.get(pipeType);
if (!pipeMeta) {
this._reportError(
syntaxError(
`Illegal state: getPipeMetadata can only be called after loadNgModuleDirectiveAndPipeMetadata for a module that declares it. Pipe ${stringifyType(pipeType)}.`),
pipeType);
}
return pipeMeta || null;
}
2016-09-14 18:58:18 -04:00
getPipeSummary(pipeType: any): cpl.CompilePipeSummary {
const pipeSummary =
<cpl.CompilePipeSummary>this._loadSummary(pipeType, cpl.CompileSummaryKind.Pipe);
if (!pipeSummary) {
this._reportError(
syntaxError(
`Illegal state: Could not load the summary for pipe ${stringifyType(pipeType)}.`),
pipeType);
}
return pipeSummary;
}
getOrLoadPipeMetadata(pipeType: any): cpl.CompilePipeMetadata {
let pipeMeta = this._pipeCache.get(pipeType);
if (!pipeMeta) {
pipeMeta = this._loadPipeMetadata(pipeType);
}
return pipeMeta;
}
private _loadPipeMetadata(pipeType: any): cpl.CompilePipeMetadata {
pipeType = resolveForwardRef(pipeType);
const pipeAnnotation = this._pipeResolver.resolve(pipeType) !;
const pipeMeta = new cpl.CompilePipeMetadata({
type: this._getTypeMetadata(pipeType),
name: pipeAnnotation.name,
pure: !!pipeAnnotation.pure
});
this._pipeCache.set(pipeType, pipeMeta);
this._summaryCache.set(pipeType, pipeMeta.toSummary());
return pipeMeta;
}
private _getDependenciesMetadata(
typeOrFunc: Type|Function, dependencies: any[]|null,
throwOnUnknownDeps = true): cpl.CompileDiDependencyMetadata[] {
let hasUnknownDeps = false;
const params = dependencies || this._reflector.parameters(typeOrFunc) || [];
2016-09-14 18:58:18 -04:00
const dependenciesMetadata: cpl.CompileDiDependencyMetadata[] = params.map((param) => {
let isAttribute = false;
let isHost = false;
let isSelf = false;
let isSkipSelf = false;
let isOptional = false;
let token: any = null;
2016-09-14 18:58:18 -04:00
if (Array.isArray(param)) {
param.forEach((paramEntry: any) => {
if (createHost.isTypeOf(paramEntry)) {
isHost = true;
} else if (createSelf.isTypeOf(paramEntry)) {
isSelf = true;
} else if (createSkipSelf.isTypeOf(paramEntry)) {
isSkipSelf = true;
} else if (createOptional.isTypeOf(paramEntry)) {
isOptional = true;
} else if (createAttribute.isTypeOf(paramEntry)) {
isAttribute = true;
token = (paramEntry as any).attributeName;
} else if (createInject.isTypeOf(paramEntry)) {
token = (paramEntry as any).token;
} else if (
createInjectionToken.isTypeOf(paramEntry) ||
(paramEntry as any) instanceof StaticSymbol) {
token = paramEntry;
} else if (isValidType(paramEntry) && token == null) {
token = paramEntry;
}
});
} else {
token = param;
}
if (token == null) {
hasUnknownDeps = true;
return {};
}
2016-09-14 18:58:18 -04:00
return {
2016-09-14 18:58:18 -04:00
isAttribute,
isHost,
isSelf,
isSkipSelf,
isOptional,
token: this._getTokenMetadata(token)
};
});
if (hasUnknownDeps) {
const depsTokens =
dependenciesMetadata.map((dep) => dep.token ? stringifyType(dep.token) : '?').join(', ');
const message =
`Can't resolve all parameters for ${stringifyType(typeOrFunc)}: (${depsTokens}).`;
if (throwOnUnknownDeps || this._config.strictInjectionParameters) {
this._reportError(syntaxError(message), typeOrFunc);
} else {
this._console.warn(`Warning: ${message} This will become an error in Angular v6.x`);
}
}
return dependenciesMetadata;
}
private _getTokenMetadata(token: any): cpl.CompileTokenMetadata {
token = resolveForwardRef(token);
2016-09-14 18:58:18 -04:00
let compileToken: cpl.CompileTokenMetadata;
2016-10-19 16:42:39 -04:00
if (typeof token === 'string') {
compileToken = {value: token};
} else {
compileToken = {identifier: {reference: token}};
}
return compileToken;
}
private _getProvidersMetadata(
providers: Provider[], targetEntryComponents: cpl.CompileEntryComponentMetadata[],
debugInfo?: string, compileProviders: cpl.CompileProviderMetadata[] = [],
type?: any): cpl.CompileProviderMetadata[] {
providers.forEach((provider: any, providerIdx: number) => {
2016-09-14 18:58:18 -04:00
if (Array.isArray(provider)) {
this._getProvidersMetadata(provider, targetEntryComponents, debugInfo, compileProviders);
} else {
provider = resolveForwardRef(provider);
let providerMeta: cpl.ProviderMeta = undefined !;
if (provider && typeof provider === 'object' && provider.hasOwnProperty('provide')) {
this._validateProvider(provider);
providerMeta = new cpl.ProviderMeta(provider.provide, provider);
} else if (isValidType(provider)) {
providerMeta = new cpl.ProviderMeta(provider, {useClass: provider});
} else if (provider === void 0) {
this._reportError(syntaxError(
`Encountered undefined provider! Usually this means you have a circular dependencies. This might be caused by using 'barrel' index.ts files.`));
return;
} else {
const providersInfo =
(<string[]>providers.reduce(
(soFar: string[], seenProvider: any, seenProviderIdx: number) => {
if (seenProviderIdx < providerIdx) {
soFar.push(`${stringifyType(seenProvider)}`);
} else if (seenProviderIdx == providerIdx) {
soFar.push(`?${stringifyType(seenProvider)}?`);
} else if (seenProviderIdx == providerIdx + 1) {
soFar.push('...');
}
return soFar;
},
[]))
.join(', ');
this._reportError(
syntaxError(
`Invalid ${debugInfo ? debugInfo : 'provider'} - only instances of Provider and Type are allowed, got: [${providersInfo}]`),
type);
return;
}
if (providerMeta.token ===
this._reflector.resolveExternalReference(Identifiers.ANALYZE_FOR_ENTRY_COMPONENTS)) {
targetEntryComponents.push(...this._getEntryComponentsFromProvider(providerMeta, type));
} else {
compileProviders.push(this.getProviderMetadata(providerMeta));
}
}
});
return compileProviders;
}
private _validateProvider(provider: any): void {
if (provider.hasOwnProperty('useClass') && provider.useClass == null) {
this._reportError(syntaxError(
`Invalid provider for ${stringifyType(provider.provide)}. useClass cannot be ${provider.useClass}.
Usually it happens when:
1. There's a circular dependency (might be caused by using index.ts (barrel) files).
2. Class was used before it was declared. Use forwardRef in this case.`));
}
}
private _getEntryComponentsFromProvider(provider: cpl.ProviderMeta, type?: any):
cpl.CompileEntryComponentMetadata[] {
const components: cpl.CompileEntryComponentMetadata[] = [];
2016-09-14 18:58:18 -04:00
const collectedIdentifiers: cpl.CompileIdentifierMetadata[] = [];
if (provider.useFactory || provider.useExisting || provider.useClass) {
this._reportError(
syntaxError(`The ANALYZE_FOR_ENTRY_COMPONENTS token only supports useValue!`), type);
return [];
}
2016-09-14 18:58:18 -04:00
if (!provider.multi) {
this._reportError(
syntaxError(`The ANALYZE_FOR_ENTRY_COMPONENTS token only supports 'multi = true'!`),
type);
return [];
}
2016-09-14 18:58:18 -04:00
extractIdentifiers(provider.useValue, collectedIdentifiers);
collectedIdentifiers.forEach((identifier) => {
const entry = this._getEntryComponentMetadata(identifier.reference, false);
if (entry) {
components.push(entry);
}
});
return components;
}
private _getEntryComponentMetadata(dirType: any, throwIfNotFound = true):
cpl.CompileEntryComponentMetadata|null {
const dirMeta = this.getNonNormalizedDirectiveMetadata(dirType);
if (dirMeta && dirMeta.metadata.isComponent) {
return {componentType: dirType, componentFactory: dirMeta.metadata.componentFactory !};
}
const dirSummary =
<cpl.CompileDirectiveSummary>this._loadSummary(dirType, cpl.CompileSummaryKind.Directive);
if (dirSummary && dirSummary.isComponent) {
return {componentType: dirType, componentFactory: dirSummary.componentFactory !};
}
if (throwIfNotFound) {
throw syntaxError(`${dirType.name} cannot be used as an entry component.`);
}
return null;
}
private _getInjectableTypeMetadata(type: Type, dependencies: any[]|null = null):
cpl.CompileTypeMetadata {
const typeSummary = this._loadSummary(type, cpl.CompileSummaryKind.Injectable);
if (typeSummary) {
return typeSummary.type;
}
return this._getTypeMetadata(type, dependencies);
}
getProviderMetadata(provider: cpl.ProviderMeta): cpl.CompileProviderMetadata {
let compileDeps: cpl.CompileDiDependencyMetadata[] = undefined !;
let compileTypeMetadata: cpl.CompileTypeMetadata = null !;
let compileFactoryMetadata: cpl.CompileFactoryMetadata = null !;
let token: cpl.CompileTokenMetadata = this._getTokenMetadata(provider.token);
2016-09-14 18:58:18 -04:00
if (provider.useClass) {
compileTypeMetadata =
this._getInjectableTypeMetadata(provider.useClass, provider.dependencies);
compileDeps = compileTypeMetadata.diDeps;
if (provider.token === provider.useClass) {
// use the compileTypeMetadata as it contains information about lifecycleHooks...
token = {identifier: compileTypeMetadata};
}
2016-09-14 18:58:18 -04:00
} else if (provider.useFactory) {
compileFactoryMetadata = this._getFactoryMetadata(provider.useFactory, provider.dependencies);
compileDeps = compileFactoryMetadata.diDeps;
}
return {
token: token,
useClass: compileTypeMetadata,
useValue: provider.useValue,
useFactory: compileFactoryMetadata,
useExisting: provider.useExisting ? this._getTokenMetadata(provider.useExisting) : undefined,
deps: compileDeps,
multi: provider.multi
};
}
private _getQueriesMetadata(
queries: {[key: string]: Query}, isViewQuery: boolean,
directiveType: Type): cpl.CompileQueryMetadata[] {
2016-09-14 18:58:18 -04:00
const res: cpl.CompileQueryMetadata[] = [];
Object.keys(queries).forEach((propertyName: string) => {
const query = queries[propertyName];
if (query.isViewQuery === isViewQuery) {
res.push(this._getQueryMetadata(query, propertyName, directiveType));
}
});
2016-09-14 18:58:18 -04:00
return res;
}
2016-09-14 18:58:18 -04:00
private _queryVarBindings(selector: any): string[] { return selector.split(/\s*,\s*/); }
private _getQueryMetadata(q: Query, propertyName: string, typeOrFunc: Type|Function):
cpl.CompileQueryMetadata {
let selectors: cpl.CompileTokenMetadata[];
2016-09-14 18:58:18 -04:00
if (typeof q.selector === 'string') {
selectors =
this._queryVarBindings(q.selector).map(varName => this._getTokenMetadata(varName));
} else {
2016-09-14 18:58:18 -04:00
if (!q.selector) {
this._reportError(
syntaxError(
`Can't construct a query for the property "${propertyName}" of "${stringifyType(typeOrFunc)}" since the query selector wasn't defined.`),
typeOrFunc);
selectors = [];
} else {
selectors = [this._getTokenMetadata(q.selector)];
}
}
2016-09-14 18:58:18 -04:00
return {
2016-09-14 18:58:18 -04:00
selectors,
first: q.first,
2016-09-14 18:58:18 -04:00
descendants: q.descendants, propertyName,
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
read: q.read ? this._getTokenMetadata(q.read) : null !,
static: q.static
};
}
private _reportError(error: any, type?: any, otherType?: any) {
if (this._errorCollector) {
this._errorCollector(error, type);
if (otherType) {
this._errorCollector(error, otherType);
}
} else {
throw error;
}
}
}
function flattenArray(tree: any[], out: Array<any> = []): Array<any> {
if (tree) {
2016-09-14 18:58:18 -04:00
for (let i = 0; i < tree.length; i++) {
const item = resolveForwardRef(tree[i]);
if (Array.isArray(item)) {
flattenArray(item, out);
} else {
out.push(item);
}
}
}
return out;
}
function dedupeArray(array: any[]): Array<any> {
if (array) {
return Array.from(new Set(array));
}
return [];
}
function flattenAndDedupeArray(tree: any[]): Array<any> {
return dedupeArray(flattenArray(tree));
}
function isValidType(value: any): boolean {
return (value instanceof StaticSymbol) || (value instanceof Type);
}
function extractIdentifiers(value: any, targetIdentifiers: cpl.CompileIdentifierMetadata[]) {
visitValue(value, new _CompileValueConverter(), targetIdentifiers);
}
class _CompileValueConverter extends ValueTransformer {
visitOther(value: any, targetIdentifiers: cpl.CompileIdentifierMetadata[]): any {
targetIdentifiers.push({reference: value});
}
}
function stringifyType(type: any): string {
if (type instanceof StaticSymbol) {
return `${type.name} in ${type.filePath}`;
} else {
return stringify(type);
}
}
/**
* Indicates that a component is still being loaded in a synchronous compile.
*/
function componentStillLoadingError(compType: Type) {
const error =
Error(`Can't compile synchronously as ${stringify(compType)} is still being loaded!`);
(error as any)[ERROR_COMPONENT_TYPE] = compType;
return error;
}