refactor(compiler): never create CompileDirectiveMetadata with not loaded resources (#12788)

Part of #12787
This commit is contained in:
Tobias Bosch 2016-11-10 14:07:30 -08:00 committed by Victor Berchet
parent c3c0e2e2a2
commit 79383ce150
14 changed files with 1165 additions and 935 deletions

View File

@ -129,10 +129,10 @@ export class CodeGenerator {
const resolver = new compiler.CompileMetadataResolver(
new compiler.NgModuleResolver(staticReflector),
new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector),
elementSchemaRegistry, staticReflector);
elementSchemaRegistry, normalizer, staticReflector);
// TODO(vicb): do not pass cliOptions.i18nFormat here
const offlineCompiler = new compiler.OfflineCompiler(
resolver, normalizer, tmplParser, new compiler.StyleCompiler(urlResolver),
resolver, tmplParser, new compiler.StyleCompiler(urlResolver),
new compiler.ViewCompiler(config, elementSchemaRegistry),
new compiler.DirectiveWrapperCompiler(
config, expressionParser, elementSchemaRegistry, console),

View File

@ -28,50 +28,40 @@ export class Extractor {
private options: tsc.AngularCompilerOptions, private program: ts.Program,
public host: ts.CompilerHost, private staticReflector: StaticReflector,
private messageBundle: compiler.MessageBundle, private reflectorHost: ReflectorHost,
private metadataResolver: compiler.CompileMetadataResolver,
private directiveNormalizer: compiler.DirectiveNormalizer) {}
private metadataResolver: compiler.CompileMetadataResolver) {}
extract(): Promise<compiler.MessageBundle> {
const programSymbols: StaticSymbol[] =
extractProgramSymbols(this.program, this.staticReflector, this.reflectorHost, this.options);
const files =
compiler.analyzeNgModules(programSymbols, {transitiveModules: true}, this.metadataResolver)
.files;
return compiler
.analyzeNgModules(programSymbols, {transitiveModules: true}, this.metadataResolver)
.then(({files}) => {
const errors: compiler.ParseError[] = [];
const filePromises: Promise<any>[] = [];
files.forEach(file => {
const cmpPromises: Promise<compiler.CompileDirectiveMetadata>[] = [];
const compMetas: compiler.CompileDirectiveMetadata[] = [];
file.directives.forEach(directiveType => {
const dirMeta = this.metadataResolver.getDirectiveMetadata(directiveType);
if (dirMeta.isComponent) {
cmpPromises.push(this.directiveNormalizer.normalizeDirective(dirMeta).asyncResult);
if (dirMeta && dirMeta.isComponent) {
compMetas.push(dirMeta);
}
});
if (cmpPromises.length) {
const done =
Promise.all(cmpPromises).then((compMetas: compiler.CompileDirectiveMetadata[]) => {
compMetas.forEach(compMeta => {
const html = compMeta.template.template;
const interpolationConfig =
compiler.InterpolationConfig.fromArray(compMeta.template.interpolation);
errors.push(...this.messageBundle.updateFromTemplate(
html, file.srcUrl, interpolationConfig));
errors.push(
...this.messageBundle.updateFromTemplate(html, file.srcUrl, interpolationConfig));
});
});
filePromises.push(done);
}
});
if (errors.length) {
throw new Error(errors.map(e => e.toString()).join('\n'));
}
return Promise.all(filePromises).then(_ => this.messageBundle);
return this.messageBundle;
});
}
static create(
@ -98,13 +88,12 @@ export class Extractor {
const resolver = new compiler.CompileMetadataResolver(
new compiler.NgModuleResolver(staticReflector),
new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector),
elementSchemaRegistry, staticReflector);
elementSchemaRegistry, normalizer, staticReflector);
// TODO(vicb): implicit tags & attributes
let messageBundle = new compiler.MessageBundle(htmlParser, [], {});
return new Extractor(
options, program, compilerHost, staticReflector, messageBundle, reflectorHost, resolver,
normalizer);
options, program, compilerHost, staticReflector, messageBundle, reflectorHost, resolver);
}
}

View File

@ -326,9 +326,7 @@ export class CompileDirectiveMetadata implements CompileMetadataWithIdentifier {
Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>,
queries?: CompileQueryMetadata[],
viewQueries?: CompileQueryMetadata[],
entryComponents?: CompileTypeMetadata[],
viewDirectives?: CompileTypeMetadata[],
viewPipes?: CompileTypeMetadata[],
entryComponents?: CompileIdentifierMetadata[],
template?: CompileTemplateMetadata
} = {}): CompileDirectiveMetadata {
var hostListeners: {[key: string]: string} = {};
@ -397,7 +395,7 @@ export class CompileDirectiveMetadata implements CompileMetadataWithIdentifier {
queries: CompileQueryMetadata[];
viewQueries: CompileQueryMetadata[];
// Note: Need to keep types here to prevent cycles!
entryComponents: CompileTypeMetadata[];
entryComponents: CompileIdentifierMetadata[];
template: CompileTemplateMetadata;
@ -421,9 +419,7 @@ export class CompileDirectiveMetadata implements CompileMetadataWithIdentifier {
Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>,
queries?: CompileQueryMetadata[],
viewQueries?: CompileQueryMetadata[],
entryComponents?: CompileTypeMetadata[],
viewDirectives?: CompileTypeMetadata[],
viewPipes?: CompileTypeMetadata[],
entryComponents?: CompileIdentifierMetadata[],
template?: CompileTemplateMetadata,
} = {}) {
this.type = type;
@ -506,13 +502,13 @@ export class CompilePipeMetadata implements CompileMetadataWithIdentifier {
*/
export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier {
type: CompileTypeMetadata;
declaredDirectives: CompileDirectiveMetadata[];
exportedDirectives: CompileDirectiveMetadata[];
declaredPipes: CompilePipeMetadata[];
exportedPipes: CompilePipeMetadata[];
declaredDirectives: CompileIdentifierMetadata[];
exportedDirectives: CompileIdentifierMetadata[];
declaredPipes: CompileIdentifierMetadata[];
exportedPipes: CompileIdentifierMetadata[];
// Note: See CompileDirectiveMetadata.entryComponents why this has to be a type.
entryComponents: CompileTypeMetadata[];
bootstrapComponents: CompileTypeMetadata[];
entryComponents: CompileIdentifierMetadata[];
bootstrapComponents: CompileIdentifierMetadata[];
providers: CompileProviderMetadata[];
importedModules: CompileNgModuleMetadata[];
@ -529,12 +525,12 @@ export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier {
type?: CompileTypeMetadata,
providers?:
Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>,
declaredDirectives?: CompileDirectiveMetadata[],
exportedDirectives?: CompileDirectiveMetadata[],
declaredPipes?: CompilePipeMetadata[],
exportedPipes?: CompilePipeMetadata[],
entryComponents?: CompileTypeMetadata[],
bootstrapComponents?: CompileTypeMetadata[],
declaredDirectives?: CompileIdentifierMetadata[],
exportedDirectives?: CompileIdentifierMetadata[],
declaredPipes?: CompileIdentifierMetadata[],
exportedPipes?: CompileIdentifierMetadata[],
entryComponents?: CompileIdentifierMetadata[],
bootstrapComponents?: CompileIdentifierMetadata[],
importedModules?: CompileNgModuleMetadata[],
exportedModules?: CompileNgModuleMetadata[],
transitiveModule?: TransitiveCompileNgModuleMetadata,
@ -560,15 +556,16 @@ export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier {
}
export class TransitiveCompileNgModuleMetadata {
directivesSet = new Set<Type<any>>();
pipesSet = new Set<Type<any>>();
directivesSet = new Set<any>();
pipesSet = new Set<any>();
constructor(
public modules: CompileNgModuleMetadata[], public providers: CompileProviderMetadata[],
public entryComponents: CompileTypeMetadata[], public directives: CompileDirectiveMetadata[],
public pipes: CompilePipeMetadata[]) {
directives.forEach(dir => this.directivesSet.add(dir.type.reference));
pipes.forEach(pipe => this.pipesSet.add(pipe.type.reference));
public entryComponents: CompileIdentifierMetadata[],
public directives: CompileIdentifierMetadata[], public pipes: CompileIdentifierMetadata[],
public loadingPromises: Promise<any>[]) {
directives.forEach(dir => this.directivesSet.add(dir.reference));
pipes.forEach(pipe => this.pipesSet.add(pipe.reference));
}
}

View File

@ -6,11 +6,11 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Injectable, ViewEncapsulation} from '@angular/core';
import {Component, Injectable, ViewEncapsulation} from '@angular/core';
import {CompileDirectiveMetadata, CompileStylesheetMetadata, CompileTemplateMetadata, CompileTypeMetadata} from './compile_metadata';
import {CompileAnimationEntryMetadata, CompileDirectiveMetadata, CompileStylesheetMetadata, CompileTemplateMetadata, CompileTypeMetadata} from './compile_metadata';
import {CompilerConfig} from './config';
import {isBlank, isPresent} from './facade/lang';
import {isBlank, isPresent, stringify} from './facade/lang';
import * as html from './ml_parser/ast';
import {HtmlParser} from './ml_parser/html_parser';
import {InterpolationConfig} from './ml_parser/interpolation_config';
@ -20,6 +20,18 @@ import {PreparsedElementType, preparseElement} from './template_parser/template_
import {UrlResolver} from './url_resolver';
import {SyncAsyncResult} from './util';
export interface PrenormalizedTemplateMetadata {
componentType: any;
moduleUrl: string;
template?: string;
templateUrl?: string;
styles?: string[];
styleUrls?: string[];
interpolation?: [string, string];
encapsulation?: ViewEncapsulation;
animations?: CompileAnimationEntryMetadata[];
}
@Injectable()
export class DirectiveNormalizer {
private _resourceLoaderCache = new Map<string, Promise<string>>();
@ -48,65 +60,56 @@ export class DirectiveNormalizer {
return result;
}
normalizeDirective(directive: CompileDirectiveMetadata):
SyncAsyncResult<CompileDirectiveMetadata> {
if (!directive.isComponent) {
// For non components there is nothing to be normalized yet.
return new SyncAsyncResult(directive, Promise.resolve(directive));
}
normalizeTemplate(prenormData: PrenormalizedTemplateMetadata):
SyncAsyncResult<CompileTemplateMetadata> {
let normalizedTemplateSync: CompileTemplateMetadata = null;
let normalizedTemplateAsync: Promise<CompileTemplateMetadata>;
if (isPresent(directive.template.template)) {
normalizedTemplateSync = this.normalizeTemplateSync(directive.type, directive.template);
if (isPresent(prenormData.template)) {
normalizedTemplateSync = this.normalizeTemplateSync(prenormData);
normalizedTemplateAsync = Promise.resolve(normalizedTemplateSync);
} else if (directive.template.templateUrl) {
normalizedTemplateAsync = this.normalizeTemplateAsync(directive.type, directive.template);
} else if (prenormData.templateUrl) {
normalizedTemplateAsync = this.normalizeTemplateAsync(prenormData);
} else {
throw new Error(`No template specified for component ${directive.type.name}`);
throw new Error(
`No template specified for component ${stringify(prenormData.componentType)}`);
}
if (normalizedTemplateSync && normalizedTemplateSync.styleUrls.length === 0) {
// sync case
let normalizedDirective = _cloneDirectiveWithTemplate(directive, normalizedTemplateSync);
return new SyncAsyncResult(normalizedDirective, Promise.resolve(normalizedDirective));
return new SyncAsyncResult(normalizedTemplateSync);
} else {
// async case
return new SyncAsyncResult(
null,
normalizedTemplateAsync
.then((normalizedTemplate) => this.normalizeExternalStylesheets(normalizedTemplate))
.then(
(normalizedTemplate) =>
_cloneDirectiveWithTemplate(directive, normalizedTemplate)));
null, normalizedTemplateAsync.then(
(normalizedTemplate) => this.normalizeExternalStylesheets(normalizedTemplate)));
}
}
normalizeTemplateSync(directiveType: CompileTypeMetadata, template: CompileTemplateMetadata):
CompileTemplateMetadata {
return this.normalizeLoadedTemplate(
directiveType, template, template.template, directiveType.moduleUrl);
normalizeTemplateSync(prenomData: PrenormalizedTemplateMetadata): CompileTemplateMetadata {
return this.normalizeLoadedTemplate(prenomData, prenomData.template, prenomData.moduleUrl);
}
normalizeTemplateAsync(directiveType: CompileTypeMetadata, template: CompileTemplateMetadata):
normalizeTemplateAsync(prenomData: PrenormalizedTemplateMetadata):
Promise<CompileTemplateMetadata> {
let templateUrl = this._urlResolver.resolve(directiveType.moduleUrl, template.templateUrl);
let templateUrl = this._urlResolver.resolve(prenomData.moduleUrl, prenomData.templateUrl);
return this._fetch(templateUrl)
.then((value) => this.normalizeLoadedTemplate(directiveType, template, value, templateUrl));
.then((value) => this.normalizeLoadedTemplate(prenomData, value, templateUrl));
}
normalizeLoadedTemplate(
directiveType: CompileTypeMetadata, templateMeta: CompileTemplateMetadata, template: string,
prenomData: PrenormalizedTemplateMetadata, template: string,
templateAbsUrl: string): CompileTemplateMetadata {
const interpolationConfig = InterpolationConfig.fromArray(templateMeta.interpolation);
const rootNodesAndErrors =
this._htmlParser.parse(template, directiveType.name, false, interpolationConfig);
const interpolationConfig = InterpolationConfig.fromArray(prenomData.interpolation);
const rootNodesAndErrors = this._htmlParser.parse(
template, stringify(prenomData.componentType), false, interpolationConfig);
if (rootNodesAndErrors.errors.length > 0) {
const errorString = rootNodesAndErrors.errors.join('\n');
throw new Error(`Template parse errors:\n${errorString}`);
}
const templateMetadataStyles = this.normalizeStylesheet(new CompileStylesheetMetadata({
styles: templateMeta.styles,
styleUrls: templateMeta.styleUrls,
moduleUrl: directiveType.moduleUrl
styles: prenomData.styles,
styleUrls: prenomData.styleUrls,
moduleUrl: prenomData.moduleUrl
}));
const visitor = new TemplatePreparseVisitor();
@ -114,7 +117,7 @@ export class DirectiveNormalizer {
const templateStyles = this.normalizeStylesheet(new CompileStylesheetMetadata(
{styles: visitor.styles, styleUrls: visitor.styleUrls, moduleUrl: templateAbsUrl}));
let encapsulation = templateMeta.encapsulation;
let encapsulation = prenomData.encapsulation;
if (isBlank(encapsulation)) {
encapsulation = this._config.defaultEncapsulation;
}
@ -131,10 +134,9 @@ export class DirectiveNormalizer {
encapsulation,
template,
templateUrl: templateAbsUrl, styles, styleUrls,
externalStylesheets: templateMeta.externalStylesheets,
ngContentSelectors: visitor.ngContentSelectors,
animations: templateMeta.animations,
interpolation: templateMeta.interpolation,
animations: prenomData.animations,
interpolation: prenomData.interpolation,
});
}
@ -231,25 +233,3 @@ class TemplatePreparseVisitor implements html.Visitor {
visitExpansion(ast: html.Expansion, context: any): any { return null; }
visitExpansionCase(ast: html.ExpansionCase, context: any): any { return null; }
}
function _cloneDirectiveWithTemplate(
directive: CompileDirectiveMetadata,
template: CompileTemplateMetadata): CompileDirectiveMetadata {
return new CompileDirectiveMetadata({
type: directive.type,
isComponent: directive.isComponent,
selector: directive.selector,
exportAs: directive.exportAs,
changeDetection: directive.changeDetection,
inputs: directive.inputs,
outputs: directive.outputs,
hostListeners: directive.hostListeners,
hostProperties: directive.hostProperties,
hostAttributes: directive.hostAttributes,
providers: directive.providers,
viewProviders: directive.viewProviders,
queries: directive.queries,
viewQueries: directive.viewQueries,
entryComponents: directive.entryComponents, template,
});
}

View File

@ -24,6 +24,11 @@ import {splitAtColon} from './util';
export class DirectiveResolver {
constructor(private _reflector: ReflectorReader = reflector) {}
isDirective(type: Type<any>) {
const typeMetadata = this._reflector.annotations(resolveForwardRef(type));
return typeMetadata && typeMetadata.some(isDirectiveMetadata);
}
/**
* Return {@link Directive} for a given `Type`.
*/

View File

@ -10,17 +10,27 @@ import {AnimationAnimateMetadata, AnimationEntryMetadata, AnimationGroupMetadata
import {assertArrayOfStrings, assertInterpolationSymbols} from './assertions';
import * as cpl from './compile_metadata';
import {DirectiveNormalizer} from './directive_normalizer';
import {DirectiveResolver} from './directive_resolver';
import {ListWrapper} from './facade/collection';
import {isBlank, isPresent, stringify} from './facade/lang';
import {Identifiers, resolveIdentifierToken} from './identifiers';
import {hasLifecycleHook} from './lifecycle_reflector';
import {NgModuleResolver} from './ng_module_resolver';
import {PipeResolver} from './pipe_resolver';
import {LIFECYCLE_HOOKS_VALUES, ReflectorReader, reflector} from './private_import_core';
import {ComponentStillLoadingError, LIFECYCLE_HOOKS_VALUES, ReflectorReader, reflector} from './private_import_core';
import {ElementSchemaRegistry} from './schema/element_schema_registry';
import {getUrlScheme} from './url_resolver';
import {MODULE_SUFFIX, ValueTransformer, sanitizeIdentifier, visitValue} from './util';
import {MODULE_SUFFIX, SyncAsyncResult, ValueTransformer, sanitizeIdentifier, visitValue} from './util';
// 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.
@Injectable()
export class CompileMetadataResolver {
private _directiveCache = new Map<Type<any>, cpl.CompileDirectiveMetadata>();
@ -33,6 +43,7 @@ export class CompileMetadataResolver {
constructor(
private _ngModuleResolver: NgModuleResolver, private _directiveResolver: DirectiveResolver,
private _pipeResolver: PipeResolver, private _schemaRegistry: ElementSchemaRegistry,
private _directiveNormalizer: DirectiveNormalizer,
private _reflector: ReflectorReader = reflector) {}
private sanitizeTokenName(token: any): string {
@ -50,11 +61,15 @@ export class CompileMetadataResolver {
}
clearCacheFor(type: Type<any>) {
const dirMeta = this._directiveCache.get(type);
this._directiveCache.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() {
@ -62,50 +77,53 @@ export class CompileMetadataResolver {
this._pipeCache.clear();
this._ngModuleCache.clear();
this._ngModuleOfTypes.clear();
this._directiveNormalizer.clearCache();
}
getAnimationEntryMetadata(entry: AnimationEntryMetadata): cpl.CompileAnimationEntryMetadata {
const defs = entry.definitions.map(def => this.getAnimationStateMetadata(def));
const defs = entry.definitions.map(def => this._getAnimationStateMetadata(def));
return new cpl.CompileAnimationEntryMetadata(entry.name, defs);
}
getAnimationStateMetadata(value: AnimationStateMetadata): cpl.CompileAnimationStateMetadata {
private _getAnimationStateMetadata(value: AnimationStateMetadata):
cpl.CompileAnimationStateMetadata {
if (value instanceof AnimationStateDeclarationMetadata) {
const styles = this.getAnimationStyleMetadata(value.styles);
const styles = this._getAnimationStyleMetadata(value.styles);
return new cpl.CompileAnimationStateDeclarationMetadata(value.stateNameExpr, styles);
}
if (value instanceof AnimationStateTransitionMetadata) {
return new cpl.CompileAnimationStateTransitionMetadata(
value.stateChangeExpr, this.getAnimationMetadata(value.steps));
value.stateChangeExpr, this._getAnimationMetadata(value.steps));
}
return null;
}
getAnimationStyleMetadata(value: AnimationStyleMetadata): cpl.CompileAnimationStyleMetadata {
private _getAnimationStyleMetadata(value: AnimationStyleMetadata):
cpl.CompileAnimationStyleMetadata {
return new cpl.CompileAnimationStyleMetadata(value.offset, value.styles);
}
getAnimationMetadata(value: AnimationMetadata): cpl.CompileAnimationMetadata {
private _getAnimationMetadata(value: AnimationMetadata): cpl.CompileAnimationMetadata {
if (value instanceof AnimationStyleMetadata) {
return this.getAnimationStyleMetadata(value);
return this._getAnimationStyleMetadata(value);
}
if (value instanceof AnimationKeyframesSequenceMetadata) {
return new cpl.CompileAnimationKeyframesSequenceMetadata(
value.steps.map(entry => this.getAnimationStyleMetadata(entry)));
value.steps.map(entry => this._getAnimationStyleMetadata(entry)));
}
if (value instanceof AnimationAnimateMetadata) {
const animateData =
<cpl.CompileAnimationStyleMetadata|cpl.CompileAnimationKeyframesSequenceMetadata>this
.getAnimationMetadata(value.styles);
._getAnimationMetadata(value.styles);
return new cpl.CompileAnimationAnimateMetadata(value.timings, animateData);
}
if (value instanceof AnimationWithStepsMetadata) {
const steps = value.steps.map(step => this.getAnimationMetadata(step));
const steps = value.steps.map(step => this._getAnimationMetadata(step));
if (value instanceof AnimationGroupMetadata) {
return new cpl.CompileAnimationGroupMetadata(steps);
@ -116,52 +134,35 @@ export class CompileMetadataResolver {
return null;
}
getDirectiveMetadata(directiveType: any, throwIfNotFound = true): cpl.CompileDirectiveMetadata {
private _loadDirectiveMetadata(directiveType: any, isSync: boolean): Promise<any> {
if (this._directiveCache.has(directiveType)) {
return;
}
directiveType = resolveForwardRef(directiveType);
let meta = this._directiveCache.get(directiveType);
if (!meta) {
const dirMeta = this._directiveResolver.resolve(directiveType, throwIfNotFound);
const dirMeta = this._directiveResolver.resolve(directiveType);
if (!dirMeta) {
return null;
}
let templateMeta: cpl.CompileTemplateMetadata = null;
let moduleUrl = staticTypeModuleUrl(directiveType);
const createDirectiveMetadata = (templateMeta: cpl.CompileTemplateMetadata) => {
let changeDetectionStrategy: ChangeDetectionStrategy = null;
let viewProviders: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = [];
let moduleUrl = staticTypeModuleUrl(directiveType);
let entryComponentMetadata: cpl.CompileTypeMetadata[] = [];
let entryComponentMetadata: cpl.CompileIdentifierMetadata[] = [];
let selector = dirMeta.selector;
if (dirMeta instanceof Component) {
// Component
assertArrayOfStrings('styles', dirMeta.styles);
assertArrayOfStrings('styleUrls', dirMeta.styleUrls);
assertInterpolationSymbols('interpolation', dirMeta.interpolation);
const animations = dirMeta.animations ?
dirMeta.animations.map(e => this.getAnimationEntryMetadata(e)) :
null;
templateMeta = new cpl.CompileTemplateMetadata({
encapsulation: dirMeta.encapsulation,
template: dirMeta.template,
templateUrl: dirMeta.templateUrl,
styles: dirMeta.styles,
styleUrls: dirMeta.styleUrls,
animations: animations,
interpolation: dirMeta.interpolation
});
changeDetectionStrategy = dirMeta.changeDetection;
if (dirMeta.viewProviders) {
viewProviders = this.getProvidersMetadata(
viewProviders = this._getProvidersMetadata(
dirMeta.viewProviders, entryComponentMetadata,
`viewProviders for "${stringify(directiveType)}"`);
}
moduleUrl = componentModuleUrl(this._reflector, directiveType, dirMeta);
if (dirMeta.entryComponents) {
entryComponentMetadata =
flattenAndDedupeArray(dirMeta.entryComponents)
.map((type) => this.getTypeMetadata(type, staticTypeModuleUrl(type)))
.map((type) => this._getIdentifierMetadata(type, staticTypeModuleUrl(type)))
.concat(entryComponentMetadata);
}
if (!selector) {
@ -176,22 +177,22 @@ export class CompileMetadataResolver {
let providers: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = [];
if (isPresent(dirMeta.providers)) {
providers = this.getProvidersMetadata(
providers = this._getProvidersMetadata(
dirMeta.providers, entryComponentMetadata,
`providers for "${stringify(directiveType)}"`);
}
let queries: cpl.CompileQueryMetadata[] = [];
let viewQueries: cpl.CompileQueryMetadata[] = [];
if (isPresent(dirMeta.queries)) {
queries = this.getQueriesMetadata(dirMeta.queries, false, directiveType);
viewQueries = this.getQueriesMetadata(dirMeta.queries, true, directiveType);
queries = this._getQueriesMetadata(dirMeta.queries, false, directiveType);
viewQueries = this._getQueriesMetadata(dirMeta.queries, true, directiveType);
}
meta = cpl.CompileDirectiveMetadata.create({
const meta = cpl.CompileDirectiveMetadata.create({
selector: selector,
exportAs: dirMeta.exportAs,
isComponent: !!templateMeta,
type: this.getTypeMetadata(directiveType, moduleUrl),
type: this._getTypeMetadata(directiveType, moduleUrl),
template: templateMeta,
changeDetection: changeDetectionStrategy,
inputs: dirMeta.inputs,
@ -204,27 +205,110 @@ export class CompileMetadataResolver {
entryComponents: entryComponentMetadata
});
this._directiveCache.set(directiveType, meta);
}
return meta;
};
if (dirMeta instanceof Component) {
// component
moduleUrl = componentModuleUrl(this._reflector, directiveType, dirMeta);
assertArrayOfStrings('styles', dirMeta.styles);
assertArrayOfStrings('styleUrls', dirMeta.styleUrls);
assertInterpolationSymbols('interpolation', dirMeta.interpolation);
const animations = dirMeta.animations ?
dirMeta.animations.map(e => this.getAnimationEntryMetadata(e)) :
null;
const templateMeta = this._directiveNormalizer.normalizeTemplate({
componentType: directiveType,
moduleUrl: moduleUrl,
encapsulation: dirMeta.encapsulation,
template: dirMeta.template,
templateUrl: dirMeta.templateUrl,
styles: dirMeta.styles,
styleUrls: dirMeta.styleUrls,
animations: animations,
interpolation: dirMeta.interpolation
});
if (templateMeta.syncResult) {
createDirectiveMetadata(templateMeta.syncResult);
return null;
} else {
if (isSync) {
throw new ComponentStillLoadingError(directiveType);
}
return templateMeta.asyncResult.then(createDirectiveMetadata);
}
} else {
// directive
createDirectiveMetadata(null);
return null;
}
}
getNgModuleMetadata(moduleType: any, throwIfNotFound = true): cpl.CompileNgModuleMetadata {
/**
* Gets the metadata for the given directive.
* This assumes `loadNgModuleMetadata` has been called first.
*/
getDirectiveMetadata(directiveType: any): cpl.CompileDirectiveMetadata {
const dirMeta = this._directiveCache.get(directiveType);
if (!dirMeta) {
throw new Error(
`Illegal state: getDirectiveMetadata can only be called after loadNgModuleMetadata for a module that declares it. Directive ${stringify(directiveType)}.`);
}
return dirMeta;
}
isDirective(type: any) { return this._directiveResolver.isDirective(type); }
isPipe(type: any) { return this._pipeResolver.isPipe(type); }
/**
* Gets the metadata for the given module.
* This assumes `loadNgModuleMetadata` has been called first.
*/
getNgModuleMetadata(moduleType: any): cpl.CompileNgModuleMetadata {
const modMeta = this._ngModuleCache.get(moduleType);
if (!modMeta) {
throw new Error(
`Illegal state: getNgModuleMetadata can only be called after loadNgModuleMetadata. Module ${stringify(moduleType)}.`);
}
return modMeta;
}
/**
* Loads an NgModule and all of its directives. This includes loading the exported directives of
* imported modules,
* but not private directives of imported modules.
*/
loadNgModuleMetadata(moduleType: any, isSync: boolean, throwIfNotFound = true):
{ngModule: cpl.CompileNgModuleMetadata, loading: Promise<any>} {
const ngModule = this._loadNgModuleMetadata(moduleType, isSync, throwIfNotFound);
const loading =
ngModule ? Promise.all(ngModule.transitiveModule.loadingPromises) : Promise.resolve(null);
return {ngModule, loading};
}
private _loadNgModuleMetadata(moduleType: any, isSync: boolean, throwIfNotFound = true):
cpl.CompileNgModuleMetadata {
moduleType = resolveForwardRef(moduleType);
let compileMeta = this._ngModuleCache.get(moduleType);
if (!compileMeta) {
if (compileMeta) {
return compileMeta;
}
const meta = this._ngModuleResolver.resolve(moduleType, throwIfNotFound);
if (!meta) {
return null;
}
const declaredDirectives: cpl.CompileDirectiveMetadata[] = [];
const exportedDirectives: cpl.CompileDirectiveMetadata[] = [];
const declaredPipes: cpl.CompilePipeMetadata[] = [];
const exportedPipes: cpl.CompilePipeMetadata[] = [];
const declaredDirectives: cpl.CompileIdentifierMetadata[] = [];
const exportedDirectives: cpl.CompileIdentifierMetadata[] = [];
const declaredPipes: cpl.CompileIdentifierMetadata[] = [];
const exportedPipes: cpl.CompileIdentifierMetadata[] = [];
const importedModules: cpl.CompileNgModuleMetadata[] = [];
const exportedModules: cpl.CompileNgModuleMetadata[] = [];
const providers: any[] = [];
const entryComponents: cpl.CompileTypeMetadata[] = [];
const bootstrapComponents: cpl.CompileTypeMetadata[] = [];
const entryComponents: cpl.CompileIdentifierMetadata[] = [];
const bootstrapComponents: cpl.CompileIdentifierMetadata[] = [];
const schemas: SchemaMetadata[] = [];
if (meta.imports) {
@ -236,14 +320,14 @@ export class CompileMetadataResolver {
const moduleWithProviders: ModuleWithProviders = importedType;
importedModuleType = moduleWithProviders.ngModule;
if (moduleWithProviders.providers) {
providers.push(...this.getProvidersMetadata(
providers.push(...this._getProvidersMetadata(
moduleWithProviders.providers, entryComponents,
`provider for the NgModule '${stringify(importedModuleType)}'`));
}
}
if (importedModuleType) {
const importedMeta = this.getNgModuleMetadata(importedModuleType, false);
const importedMeta = this._loadNgModuleMetadata(importedModuleType, isSync, false);
if (importedMeta === null) {
throw new Error(
`Unexpected ${this._getTypeDescriptor(importedType)} '${stringify(importedType)}' imported by the module '${stringify(moduleType)}'`);
@ -262,14 +346,14 @@ export class CompileMetadataResolver {
throw new Error(
`Unexpected value '${stringify(exportedType)}' exported by the module '${stringify(moduleType)}'`);
}
let exportedDirMeta: cpl.CompileDirectiveMetadata;
let exportedPipeMeta: cpl.CompilePipeMetadata;
let identifier =
this._getIdentifierMetadata(exportedType, staticTypeModuleUrl(exportedType));
let exportedModuleMeta: cpl.CompileNgModuleMetadata;
if (exportedDirMeta = this.getDirectiveMetadata(exportedType, false)) {
exportedDirectives.push(exportedDirMeta);
} else if (exportedPipeMeta = this.getPipeMetadata(exportedType, false)) {
exportedPipes.push(exportedPipeMeta);
} else if (exportedModuleMeta = this.getNgModuleMetadata(exportedType, false)) {
if (this._directiveResolver.isDirective(exportedType)) {
exportedDirectives.push(identifier);
} else if (this._pipeResolver.isPipe(exportedType)) {
exportedPipes.push(identifier);
} else if (exportedModuleMeta = this._loadNgModuleMetadata(exportedType, isSync, false)) {
exportedModules.push(exportedModuleMeta);
} else {
throw new Error(
@ -280,22 +364,30 @@ export class CompileMetadataResolver {
// Note: This will be modified later, so we rely on
// getting a new instance every time!
const transitiveModule =
this._getTransitiveNgModuleMetadata(importedModules, exportedModules);
const transitiveModule = this._getTransitiveNgModuleMetadata(importedModules, exportedModules);
if (meta.declarations) {
flattenAndDedupeArray(meta.declarations).forEach((declaredType) => {
if (!isValidType(declaredType)) {
throw new Error(
`Unexpected value '${stringify(declaredType)}' declared by the module '${stringify(moduleType)}'`);
}
let declaredDirMeta: cpl.CompileDirectiveMetadata;
let declaredPipeMeta: cpl.CompilePipeMetadata;
if (declaredDirMeta = this.getDirectiveMetadata(declaredType, false)) {
this._addDirectiveToModule(
declaredDirMeta, moduleType, transitiveModule, declaredDirectives, true);
} else if (declaredPipeMeta = this.getPipeMetadata(declaredType, false)) {
this._addPipeToModule(
declaredPipeMeta, moduleType, transitiveModule, declaredPipes, true);
const declaredIdentifier =
this._getIdentifierMetadata(declaredType, staticTypeModuleUrl(declaredType));
if (this._directiveResolver.isDirective(declaredType)) {
transitiveModule.directivesSet.add(declaredType);
transitiveModule.directives.push(declaredIdentifier);
declaredDirectives.push(declaredIdentifier);
this._addTypeToModule(declaredType, moduleType);
const loadingPromise = this._loadDirectiveMetadata(declaredType, isSync);
if (loadingPromise) {
transitiveModule.loadingPromises.push(loadingPromise);
}
} else if (this._pipeResolver.isPipe(declaredType)) {
transitiveModule.pipesSet.add(declaredType);
transitiveModule.pipes.push(declaredIdentifier);
declaredPipes.push(declaredIdentifier);
this._addTypeToModule(declaredType, moduleType);
this._loadPipeMetadata(declaredType);
} else {
throw new Error(
`Unexpected ${this._getTypeDescriptor(declaredType)} '${stringify(declaredType)}' declared by the module '${stringify(moduleType)}'`);
@ -306,15 +398,14 @@ export class CompileMetadataResolver {
// 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 '${stringify(moduleType)}'`));
providers.push(...this._getProvidersMetadata(
meta.providers, entryComponents, `provider for the NgModule '${stringify(moduleType)}'`));
}
if (meta.entryComponents) {
entryComponents.push(
...flattenAndDedupeArray(meta.entryComponents)
.map(type => this.getTypeMetadata(type, staticTypeModuleUrl(type))));
.map(type => this._getTypeMetadata(type, staticTypeModuleUrl(type))));
}
if (meta.bootstrap) {
@ -323,7 +414,7 @@ export class CompileMetadataResolver {
throw new Error(
`Unexpected value '${stringify(type)}' used in the bootstrap property of module '${stringify(moduleType)}'`);
}
return this.getTypeMetadata(type, staticTypeModuleUrl(type));
return this._getTypeMetadata(type, staticTypeModuleUrl(type));
});
bootstrapComponents.push(...typeMetadata);
}
@ -338,7 +429,7 @@ export class CompileMetadataResolver {
transitiveModule.providers.push(...providers);
compileMeta = new cpl.CompileNgModuleMetadata({
type: this.getTypeMetadata(moduleType, staticTypeModuleUrl(moduleType)),
type: this._getTypeMetadata(moduleType, staticTypeModuleUrl(moduleType)),
providers,
entryComponents,
bootstrapComponents,
@ -356,37 +447,36 @@ export class CompileMetadataResolver {
transitiveModule.modules.push(compileMeta);
this._verifyModule(compileMeta);
this._ngModuleCache.set(moduleType, compileMeta);
}
return compileMeta;
}
private _verifyModule(moduleMeta: cpl.CompileNgModuleMetadata) {
moduleMeta.exportedDirectives.forEach((dirMeta) => {
if (!moduleMeta.transitiveModule.directivesSet.has(dirMeta.type.reference)) {
moduleMeta.exportedDirectives.forEach((dirIdentifier) => {
if (!moduleMeta.transitiveModule.directivesSet.has(dirIdentifier.reference)) {
throw new Error(
`Can't export directive ${stringify(dirMeta.type.reference)} from ${stringify(moduleMeta.type.reference)} as it was neither declared nor imported!`);
`Can't export directive ${stringify(dirIdentifier.reference)} from ${stringify(moduleMeta.type.reference)} as it was neither declared nor imported!`);
}
});
moduleMeta.exportedPipes.forEach((pipeMeta) => {
if (!moduleMeta.transitiveModule.pipesSet.has(pipeMeta.type.reference)) {
moduleMeta.exportedPipes.forEach((pipeIdentifier) => {
if (!moduleMeta.transitiveModule.pipesSet.has(pipeIdentifier.reference)) {
throw new Error(
`Can't export pipe ${stringify(pipeMeta.type.reference)} from ${stringify(moduleMeta.type.reference)} as it was neither declared nor imported!`);
`Can't export pipe ${stringify(pipeIdentifier.reference)} from ${stringify(moduleMeta.type.reference)} as it was neither declared nor imported!`);
}
});
}
private _getTypeDescriptor(type: Type<any>): string {
if (this._directiveResolver.resolve(type, false)) {
if (this._directiveResolver.isDirective(type)) {
return 'directive';
}
if (this._pipeResolver.resolve(type, false)) {
if (this._pipeResolver.isPipe(type)) {
return 'pipe';
}
if (this._ngModuleResolver.resolve(type, false)) {
if (this._ngModuleResolver.isNgModule(type)) {
return 'module';
}
@ -397,6 +487,7 @@ export class CompileMetadataResolver {
return 'value';
}
private _addTypeToModule(type: Type<any>, moduleType: Type<any>) {
const oldModule = this._ngModuleOfTypes.get(type);
if (oldModule && oldModule !== moduleType) {
@ -421,81 +512,72 @@ export class CompileMetadataResolver {
const directives =
flattenArray(transitiveExportedModules.map((ngModule) => ngModule.exportedDirectives));
const pipes = flattenArray(transitiveExportedModules.map((ngModule) => ngModule.exportedPipes));
const loadingPromises = ListWrapper.flatten(
transitiveExportedModules.map(ngModule => ngModule.transitiveModule.loadingPromises));
return new cpl.TransitiveCompileNgModuleMetadata(
transitiveModules, providers, entryComponents, directives, pipes);
transitiveModules, providers, entryComponents, directives, pipes, loadingPromises);
}
private _addDirectiveToModule(
dirMeta: cpl.CompileDirectiveMetadata, moduleType: any,
transitiveModule: cpl.TransitiveCompileNgModuleMetadata,
declaredDirectives: cpl.CompileDirectiveMetadata[], force: boolean = false): boolean {
if (force || !transitiveModule.directivesSet.has(dirMeta.type.reference)) {
transitiveModule.directivesSet.add(dirMeta.type.reference);
transitiveModule.directives.push(dirMeta);
declaredDirectives.push(dirMeta);
this._addTypeToModule(dirMeta.type.reference, moduleType);
return true;
}
return false;
}
private _addPipeToModule(
pipeMeta: cpl.CompilePipeMetadata, moduleType: any,
transitiveModule: cpl.TransitiveCompileNgModuleMetadata,
declaredPipes: cpl.CompilePipeMetadata[], force: boolean = false): boolean {
if (force || !transitiveModule.pipesSet.has(pipeMeta.type.reference)) {
transitiveModule.pipesSet.add(pipeMeta.type.reference);
transitiveModule.pipes.push(pipeMeta);
declaredPipes.push(pipeMeta);
this._addTypeToModule(pipeMeta.type.reference, moduleType);
return true;
}
return false;
}
getTypeMetadata(type: Type<any>, moduleUrl: string, dependencies: any[] = null):
cpl.CompileTypeMetadata {
private _getIdentifierMetadata(type: Type<any>, moduleUrl: string):
cpl.CompileIdentifierMetadata {
type = resolveForwardRef(type);
return new cpl.CompileIdentifierMetadata(
{name: this.sanitizeTokenName(type), moduleUrl, reference: type});
}
private _getTypeMetadata(type: Type<any>, moduleUrl: string, dependencies: any[] = null):
cpl.CompileTypeMetadata {
const identifier = this._getIdentifierMetadata(type, moduleUrl);
return new cpl.CompileTypeMetadata({
name: this.sanitizeTokenName(type),
moduleUrl,
reference: type,
diDeps: this.getDependenciesMetadata(type, dependencies),
lifecycleHooks: LIFECYCLE_HOOKS_VALUES.filter(hook => hasLifecycleHook(hook, type)),
name: identifier.name,
moduleUrl: identifier.moduleUrl,
reference: identifier.reference,
diDeps: this._getDependenciesMetadata(identifier.reference, dependencies),
lifecycleHooks:
LIFECYCLE_HOOKS_VALUES.filter(hook => hasLifecycleHook(hook, identifier.reference)),
});
}
getFactoryMetadata(factory: Function, moduleUrl: string, dependencies: any[] = null):
private _getFactoryMetadata(factory: Function, moduleUrl: string, dependencies: any[] = null):
cpl.CompileFactoryMetadata {
factory = resolveForwardRef(factory);
return new cpl.CompileFactoryMetadata({
name: this.sanitizeTokenName(factory),
moduleUrl,
reference: factory,
diDeps: this.getDependenciesMetadata(factory, dependencies)
diDeps: this._getDependenciesMetadata(factory, dependencies)
});
}
getPipeMetadata(pipeType: Type<any>, throwIfNotFound = true): cpl.CompilePipeMetadata {
/**
* Gets the metadata for the given pipe.
* This assumes `loadNgModuleMetadata` has been called first.
*/
getPipeMetadata(pipeType: any): cpl.CompilePipeMetadata {
const pipeMeta = this._pipeCache.get(pipeType);
if (!pipeMeta) {
throw new Error(
`Illegal state: getPipeMetadata can only be called after loadNgModuleMetadata for a module that declares it. Pipe ${stringify(pipeType)}.`);
}
return pipeMeta;
}
private _loadPipeMetadata(pipeType: Type<any>): void {
pipeType = resolveForwardRef(pipeType);
let meta = this._pipeCache.get(pipeType);
if (!meta) {
const pipeMeta = this._pipeResolver.resolve(pipeType, throwIfNotFound);
const pipeMeta = this._pipeResolver.resolve(pipeType);
if (!pipeMeta) {
return null;
}
meta = new cpl.CompilePipeMetadata({
type: this.getTypeMetadata(pipeType, staticTypeModuleUrl(pipeType)),
const meta = new cpl.CompilePipeMetadata({
type: this._getTypeMetadata(pipeType, staticTypeModuleUrl(pipeType)),
name: pipeMeta.name,
pure: pipeMeta.pure
});
this._pipeCache.set(pipeType, meta);
}
return meta;
}
getDependenciesMetadata(typeOrFunc: Type<any>|Function, dependencies: any[]):
private _getDependenciesMetadata(typeOrFunc: Type<any>|Function, dependencies: any[]):
cpl.CompileDiDependencyMetadata[] {
let hasUnknownDeps = false;
let params = dependencies || this._reflector.parameters(typeOrFunc) || [];
@ -540,7 +622,7 @@ export class CompileMetadataResolver {
isSelf,
isSkipSelf,
isOptional,
token: this.getTokenMetadata(token)
token: this._getTokenMetadata(token)
});
});
@ -555,7 +637,7 @@ export class CompileMetadataResolver {
return dependenciesMetadata;
}
getTokenMetadata(token: any): cpl.CompileTokenMetadata {
private _getTokenMetadata(token: any): cpl.CompileTokenMetadata {
token = resolveForwardRef(token);
let compileToken: cpl.CompileTokenMetadata;
if (typeof token === 'string') {
@ -572,8 +654,8 @@ export class CompileMetadataResolver {
return compileToken;
}
getProvidersMetadata(
providers: Provider[], targetEntryComponents: cpl.CompileTypeMetadata[],
private _getProvidersMetadata(
providers: Provider[], targetEntryComponents: cpl.CompileIdentifierMetadata[],
debugInfo?: string): Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> {
const compileProviders: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = [];
providers.forEach((provider: any, providerIdx: number) => {
@ -583,9 +665,9 @@ export class CompileMetadataResolver {
}
let compileProvider: cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[];
if (Array.isArray(provider)) {
compileProvider = this.getProvidersMetadata(provider, targetEntryComponents, debugInfo);
compileProvider = this._getProvidersMetadata(provider, targetEntryComponents, debugInfo);
} else if (provider instanceof cpl.ProviderMeta) {
let tokenMeta = this.getTokenMetadata(provider.token);
let tokenMeta = this._getTokenMetadata(provider.token);
if (tokenMeta.reference ===
resolveIdentifierToken(Identifiers.ANALYZE_FOR_ENTRY_COMPONENTS).reference) {
targetEntryComponents.push(...this._getEntryComponentsFromProvider(provider));
@ -593,7 +675,7 @@ export class CompileMetadataResolver {
compileProvider = this.getProviderMetadata(provider);
}
} else if (isValidType(provider)) {
compileProvider = this.getTypeMetadata(provider, staticTypeModuleUrl(provider));
compileProvider = this._getTypeMetadata(provider, staticTypeModuleUrl(provider));
} else {
const providersInfo =
(<string[]>providers.reduce(
@ -620,8 +702,9 @@ export class CompileMetadataResolver {
return compileProviders;
}
private _getEntryComponentsFromProvider(provider: cpl.ProviderMeta): cpl.CompileTypeMetadata[] {
const components: cpl.CompileTypeMetadata[] = [];
private _getEntryComponentsFromProvider(provider: cpl.ProviderMeta):
cpl.CompileIdentifierMetadata[] {
const components: cpl.CompileIdentifierMetadata[] = [];
const collectedIdentifiers: cpl.CompileIdentifierMetadata[] = [];
if (provider.useFactory || provider.useExisting || provider.useClass) {
@ -634,9 +717,8 @@ export class CompileMetadataResolver {
convertToCompileValue(provider.useValue, collectedIdentifiers);
collectedIdentifiers.forEach((identifier) => {
const dirMeta = this.getDirectiveMetadata(identifier.reference, false);
if (dirMeta) {
components.push(dirMeta.type);
if (this._directiveResolver.isDirective(identifier.reference)) {
components.push(identifier);
}
});
return components;
@ -648,27 +730,27 @@ export class CompileMetadataResolver {
let compileFactoryMetadata: cpl.CompileFactoryMetadata = null;
if (provider.useClass) {
compileTypeMetadata = this.getTypeMetadata(
compileTypeMetadata = this._getTypeMetadata(
provider.useClass, staticTypeModuleUrl(provider.useClass), provider.dependencies);
compileDeps = compileTypeMetadata.diDeps;
} else if (provider.useFactory) {
compileFactoryMetadata = this.getFactoryMetadata(
compileFactoryMetadata = this._getFactoryMetadata(
provider.useFactory, staticTypeModuleUrl(provider.useFactory), provider.dependencies);
compileDeps = compileFactoryMetadata.diDeps;
}
return new cpl.CompileProviderMetadata({
token: this.getTokenMetadata(provider.token),
token: this._getTokenMetadata(provider.token),
useClass: compileTypeMetadata,
useValue: convertToCompileValue(provider.useValue, []),
useFactory: compileFactoryMetadata,
useExisting: provider.useExisting ? this.getTokenMetadata(provider.useExisting) : null,
useExisting: provider.useExisting ? this._getTokenMetadata(provider.useExisting) : null,
deps: compileDeps,
multi: provider.multi
});
}
getQueriesMetadata(
private _getQueriesMetadata(
queries: {[key: string]: Query}, isViewQuery: boolean,
directiveType: Type<any>): cpl.CompileQueryMetadata[] {
const res: cpl.CompileQueryMetadata[] = [];
@ -676,7 +758,7 @@ export class CompileMetadataResolver {
Object.keys(queries).forEach((propertyName: string) => {
const query = queries[propertyName];
if (query.isViewQuery === isViewQuery) {
res.push(this.getQueryMetadata(query, propertyName, directiveType));
res.push(this._getQueryMetadata(query, propertyName, directiveType));
}
});
@ -685,24 +767,25 @@ export class CompileMetadataResolver {
private _queryVarBindings(selector: any): string[] { return selector.split(/\s*,\s*/); }
getQueryMetadata(q: Query, propertyName: string, typeOrFunc: Type<any>|Function):
private _getQueryMetadata(q: Query, propertyName: string, typeOrFunc: Type<any>|Function):
cpl.CompileQueryMetadata {
var selectors: cpl.CompileTokenMetadata[];
if (typeof q.selector === 'string') {
selectors = this._queryVarBindings(q.selector).map(varName => this.getTokenMetadata(varName));
selectors =
this._queryVarBindings(q.selector).map(varName => this._getTokenMetadata(varName));
} else {
if (!q.selector) {
throw new Error(
`Can't construct a query for the property "${propertyName}" of "${stringify(typeOrFunc)}" since the query selector wasn't defined.`);
}
selectors = [this.getTokenMetadata(q.selector)];
selectors = [this._getTokenMetadata(q.selector)];
}
return new cpl.CompileQueryMetadata({
selectors,
first: q.first,
descendants: q.descendants, propertyName,
read: q.read ? this.getTokenMetadata(q.read) : null
read: q.read ? this._getTokenMetadata(q.read) : null
});
}
}

View File

@ -22,6 +22,8 @@ function _isNgModuleMetadata(obj: any): obj is NgModule {
export class NgModuleResolver {
constructor(private _reflector: ReflectorReader = reflector) {}
isNgModule(type: any) { return this._reflector.annotations(type).some(_isNgModuleMetadata); }
resolve(type: Type<any>, throwIfNotFound = true): NgModule {
const ngModuleMeta: NgModule = this._reflector.annotations(type).find(_isNgModuleMetadata);

View File

@ -30,28 +30,16 @@ export class SourceModule {
// Returns all the source files and a mapping from modules to directives
export function analyzeNgModules(
programStaticSymbols: StaticSymbol[], options: {transitiveModules: boolean},
metadataResolver: CompileMetadataResolver): {
metadataResolver: CompileMetadataResolver): Promise<{
ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
files: Array<{srcUrl: string, directives: StaticSymbol[], ngModules: StaticSymbol[]}>
} {
const {
ngModules: programNgModules,
pipesAndDirectives: programPipesOrDirectives,
} = _extractModulesAndPipesOrDirectives(programStaticSymbols, metadataResolver);
const moduleMetasByRef = new Map<any, CompileNgModuleMetadata>();
programNgModules.forEach(modMeta => {
if (options.transitiveModules) {
// For every input modules add the list of transitively included modules
modMeta.transitiveModule.modules.forEach(
modMeta => { moduleMetasByRef.set(modMeta.type.reference, modMeta); });
} else {
moduleMetasByRef.set(modMeta.type.reference, modMeta);
}> {
return _loadNgModules(programStaticSymbols, options, metadataResolver).then(_analyzeNgModules);
}
});
const ngModuleMetas = Array.from(moduleMetasByRef.values());
function _analyzeNgModules(ngModuleMetas: CompileNgModuleMetadata[]) {
const moduleMetasByRef = new Map<any, CompileNgModuleMetadata>();
ngModuleMetas.forEach((ngModule) => moduleMetasByRef.set(ngModule.type.reference, ngModule));
const ngModuleByPipeOrDirective = new Map<StaticSymbol, CompileNgModuleMetadata>();
const ngModulesByFile = new Map<string, StaticSymbol[]>();
const ngDirectivesByFile = new Map<string, StaticSymbol[]>();
@ -67,31 +55,20 @@ export function analyzeNgModules(
ngModulesByFile.set(
srcFileUrl, (ngModulesByFile.get(srcFileUrl) || []).concat(ngModuleMeta.type.reference));
ngModuleMeta.declaredDirectives.forEach((dirMeta: CompileDirectiveMetadata) => {
const fileUrl = dirMeta.type.reference.filePath;
ngModuleMeta.declaredDirectives.forEach((dirIdentifier) => {
const fileUrl = dirIdentifier.reference.filePath;
filePaths.add(fileUrl);
ngDirectivesByFile.set(
fileUrl, (ngDirectivesByFile.get(fileUrl) || []).concat(dirMeta.type.reference));
ngModuleByPipeOrDirective.set(dirMeta.type.reference, ngModuleMeta);
fileUrl, (ngDirectivesByFile.get(fileUrl) || []).concat(dirIdentifier.reference));
ngModuleByPipeOrDirective.set(dirIdentifier.reference, ngModuleMeta);
});
ngModuleMeta.declaredPipes.forEach((pipeMeta: CompilePipeMetadata) => {
const fileUrl = pipeMeta.type.reference.filePath;
ngModuleMeta.declaredPipes.forEach((pipeIdentifier) => {
const fileUrl = pipeIdentifier.reference.filePath;
filePaths.add(fileUrl);
ngModuleByPipeOrDirective.set(pipeMeta.type.reference, ngModuleMeta);
ngModuleByPipeOrDirective.set(pipeIdentifier.reference, ngModuleMeta);
});
});
// Throw an error if any of the program pipe or directives is not declared by a module
const symbolsMissingModule =
programPipesOrDirectives.filter(s => !ngModuleByPipeOrDirective.has(s));
if (symbolsMissingModule.length) {
const messages = symbolsMissingModule.map(
s => `Cannot determine the module for class ${s.name} in ${s.filePath}!`);
throw new Error(messages.join('\n'));
}
const files: {srcUrl: string, directives: StaticSymbol[], ngModules: StaticSymbol[]}[] = [];
filePaths.forEach((srcUrl) => {
@ -112,35 +89,29 @@ export class OfflineCompiler {
private _animationCompiler = new AnimationCompiler();
constructor(
private _metadataResolver: CompileMetadataResolver,
private _directiveNormalizer: DirectiveNormalizer, private _templateParser: TemplateParser,
private _metadataResolver: CompileMetadataResolver, private _templateParser: TemplateParser,
private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler,
private _dirWrapperCompiler: DirectiveWrapperCompiler,
private _ngModuleCompiler: NgModuleCompiler, private _outputEmitter: OutputEmitter,
private _localeId: string, private _translationFormat: string,
private _animationParser: AnimationParser) {}
clearCache() {
this._directiveNormalizer.clearCache();
this._metadataResolver.clearCache();
}
clearCache() { this._metadataResolver.clearCache(); }
compileModules(staticSymbols: StaticSymbol[], options: {transitiveModules: boolean}):
Promise<SourceModule[]> {
const {ngModuleByPipeOrDirective, files} =
analyzeNgModules(staticSymbols, options, this._metadataResolver);
return analyzeNgModules(staticSymbols, options, this._metadataResolver)
.then(({ngModuleByPipeOrDirective, files}) => {
const sourceModules = files.map(
file => this._compileSrcFile(
file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.ngModules));
return Promise.all(sourceModules)
.then((modules: SourceModule[][]) => ListWrapper.flatten(modules));
return ListWrapper.flatten(sourceModules);
});
}
private _compileSrcFile(
srcFileUrl: string, ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
directives: StaticSymbol[], ngModules: StaticSymbol[]): Promise<SourceModule[]> {
directives: StaticSymbol[], ngModules: StaticSymbol[]): SourceModule[] {
const fileSuffix = _splitTypescriptSuffix(srcFileUrl)[1];
const statements: o.Statement[] = [];
const exportedVars: string[] = [];
@ -155,8 +126,7 @@ export class OfflineCompiler {
(directiveType) => this._compileDirectiveWrapper(directiveType, statements)));
// compile components
return Promise
.all(directives.map((dirType) => {
directives.forEach((dirType) => {
const compMeta = this._metadataResolver.getDirectiveMetadata(<any>dirType);
if (!compMeta.isComponent) {
return Promise.resolve(null);
@ -167,36 +137,27 @@ export class OfflineCompiler {
`Internal Error: cannot determine the module for component ${compMeta.type.name}!`);
}
return Promise
.all([compMeta, ...ngModule.transitiveModule.directives].map(
dirMeta => this._directiveNormalizer.normalizeDirective(dirMeta).asyncResult))
.then((normalizedCompWithDirectives) => {
const [compMeta, ...dirMetas] = normalizedCompWithDirectives;
_assertComponent(compMeta);
// compile styles
const stylesCompileResults = this._styleCompiler.compileComponent(compMeta);
stylesCompileResults.externalStylesheets.forEach((compiledStyleSheet) => {
outputSourceModules.push(
this._codgenStyles(srcFileUrl, compiledStyleSheet, fileSuffix));
outputSourceModules.push(this._codgenStyles(srcFileUrl, compiledStyleSheet, fileSuffix));
});
// compile components
exportedVars.push(
this._compileComponentFactory(compMeta, fileSuffix, statements),
this._compileComponentFactory(compMeta, ngModule, fileSuffix, statements),
this._compileComponent(
compMeta, dirMetas, ngModule.transitiveModule.pipes, ngModule.schemas,
compMeta, ngModule, ngModule.transitiveModule.directives,
stylesCompileResults.componentStylesheet, fileSuffix, statements));
});
}))
.then(() => {
if (statements.length > 0) {
const srcModule = this._codegenSourceModule(
srcFileUrl, _ngfactoryModuleUrl(srcFileUrl), statements, exportedVars);
outputSourceModules.unshift(srcModule);
}
return outputSourceModules;
});
}
private _compileModule(ngModuleType: StaticSymbol, targetStatements: o.Statement[]): string {
@ -238,11 +199,11 @@ export class OfflineCompiler {
}
private _compileComponentFactory(
compMeta: CompileDirectiveMetadata, fileSuffix: string,
compMeta: CompileDirectiveMetadata, ngModule: CompileNgModuleMetadata, fileSuffix: string,
targetStatements: o.Statement[]): string {
const hostMeta = createHostComponentMeta(compMeta);
const hostViewFactoryVar =
this._compileComponent(hostMeta, [compMeta], [], [], null, fileSuffix, targetStatements);
const hostViewFactoryVar = this._compileComponent(
hostMeta, ngModule, [compMeta.type], null, fileSuffix, targetStatements);
const compFactoryVar = _componentFactoryName(compMeta.type);
targetStatements.push(
o.variable(compFactoryVar)
@ -262,12 +223,18 @@ export class OfflineCompiler {
}
private _compileComponent(
compMeta: CompileDirectiveMetadata, directives: CompileDirectiveMetadata[],
pipes: CompilePipeMetadata[], schemas: SchemaMetadata[], componentStyles: CompiledStylesheet,
compMeta: CompileDirectiveMetadata, ngModule: CompileNgModuleMetadata,
directiveIdentifiers: CompileIdentifierMetadata[], componentStyles: CompiledStylesheet,
fileSuffix: string, targetStatements: o.Statement[]): string {
const parsedAnimations = this._animationParser.parseComponent(compMeta);
const directives =
directiveIdentifiers.map(dir => this._metadataResolver.getDirectiveMetadata(dir.reference));
const pipes = ngModule.transitiveModule.pipes.map(
pipe => this._metadataResolver.getPipeMetadata(pipe.reference));
const parsedTemplate = this._templateParser.parse(
compMeta, compMeta.template.template, directives, pipes, schemas, compMeta.type.name);
compMeta, compMeta.template.template, directives, pipes, ngModule.schemas,
compMeta.type.name);
const stylesExpr = componentStyles ? o.variable(componentStyles.stylesVar) : o.literalArr([]);
const compiledAnimations =
this._animationCompiler.compile(compMeta.type.name, parsedAnimations);
@ -358,27 +325,50 @@ function _splitTypescriptSuffix(path: string): string[] {
return [path, ''];
}
// Group the symbols by types:
// - NgModules,
// - Pipes and Directives.
function _extractModulesAndPipesOrDirectives(
programStaticSymbols: StaticSymbol[], metadataResolver: CompileMetadataResolver) {
const ngModules: CompileNgModuleMetadata[] = [];
const pipesAndDirectives: StaticSymbol[] = [];
programStaticSymbols.forEach(staticSymbol => {
const ngModule = metadataResolver.getNgModuleMetadata(staticSymbol, false);
const directive = metadataResolver.getDirectiveMetadata(staticSymbol, false);
const pipe = metadataResolver.getPipeMetadata(<any>staticSymbol, false);
// Load the NgModules and check
// that all directives / pipes that are present in the program
// are also declared by a module.
function _loadNgModules(
programStaticSymbols: StaticSymbol[], options: {transitiveModules: boolean},
metadataResolver: CompileMetadataResolver): Promise<CompileNgModuleMetadata[]> {
const ngModules = new Map<any, CompileNgModuleMetadata>();
const programPipesAndDirectives: StaticSymbol[] = [];
const ngModulePipesAndDirective = new Set<StaticSymbol>();
const loadingPromises: Promise<any>[] = [];
const addNgModule = (staticSymbol: any) => {
if (ngModules.has(staticSymbol)) {
return false;
}
const {ngModule, loading} = metadataResolver.loadNgModuleMetadata(staticSymbol, false, false);
if (ngModule) {
ngModules.push(ngModule);
} else if (directive) {
pipesAndDirectives.push(staticSymbol);
} else if (pipe) {
pipesAndDirectives.push(staticSymbol);
ngModules.set(ngModule.type.reference, ngModule);
loadingPromises.push(loading);
ngModule.declaredDirectives.forEach((dir) => ngModulePipesAndDirective.add(dir.reference));
ngModule.declaredPipes.forEach((pipe) => ngModulePipesAndDirective.add(pipe.reference));
if (options.transitiveModules) {
// For every input modules add the list of transitively included modules
ngModule.transitiveModule.modules.forEach(modMeta => addNgModule(modMeta.type.reference));
}
}
return !!ngModule;
};
programStaticSymbols.forEach((staticSymbol) => {
if (!addNgModule(staticSymbol) &&
(metadataResolver.isDirective(staticSymbol) || metadataResolver.isPipe(staticSymbol))) {
programPipesAndDirectives.push(staticSymbol);
}
});
return {ngModules, pipesAndDirectives};
// Throw an error if any of the program pipe or directives is not declared by a module
const symbolsMissingModule =
programPipesAndDirectives.filter(s => !ngModulePipesAndDirective.has(s));
if (symbolsMissingModule.length) {
const messages = symbolsMissingModule.map(
s => `Cannot determine the module for class ${s.name} in ${s.filePath}!`);
throw new Error(messages.join('\n'));
}
return Promise.all(loadingPromises).then(() => Array.from(ngModules.values()));
}

View File

@ -26,6 +26,11 @@ function _isPipeMetadata(type: any): boolean {
export class PipeResolver {
constructor(private _reflector: ReflectorReader = reflector) {}
isPipe(type: Type<any>) {
const typeMetadata = this._reflector.annotations(resolveForwardRef(type));
return typeMetadata && typeMetadata.some(_isPipeMetadata);
}
/**
* Return {@link Pipe} for a given `Type`.
*/

View File

@ -20,7 +20,6 @@ import {NgModuleCompiler} from './ng_module_compiler';
import * as ir from './output/output_ast';
import {interpretStatements} from './output/output_interpreter';
import {jitStatements} from './output/output_jit';
import {ComponentStillLoadingError} from './private_import_core';
import {CompiledStylesheet, StyleCompiler} from './style_compiler';
import {TemplateParser} from './template_parser/template_parser';
import {SyncAsyncResult} from './util';
@ -47,9 +46,8 @@ export class RuntimeCompiler implements Compiler {
constructor(
private _injector: Injector, private _metadataResolver: CompileMetadataResolver,
private _templateNormalizer: DirectiveNormalizer, private _templateParser: TemplateParser,
private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler,
private _ngModuleCompiler: NgModuleCompiler,
private _templateParser: TemplateParser, private _styleCompiler: StyleCompiler,
private _viewCompiler: ViewCompiler, private _ngModuleCompiler: NgModuleCompiler,
private _directiveWrapperCompiler: DirectiveWrapperCompiler,
private _compilerConfig: CompilerConfig, private _animationParser: AnimationParser) {}
@ -74,38 +72,47 @@ export class RuntimeCompiler implements Compiler {
private _compileModuleAndComponents<T>(moduleType: Type<T>, isSync: boolean):
SyncAsyncResult<NgModuleFactory<T>> {
const componentPromise = this._compileComponents(moduleType, isSync);
const ngModuleFactory = this._compileModule(moduleType);
return new SyncAsyncResult(ngModuleFactory, componentPromise.then(() => ngModuleFactory));
const loadingPromise = this._loadModules(moduleType, isSync);
const createResult = () => {
this._compileComponents(moduleType, null);
return this._compileModule(moduleType);
};
if (isSync) {
return new SyncAsyncResult(createResult());
} else {
return new SyncAsyncResult(null, loadingPromise.then(createResult));
}
}
private _compileModuleAndAllComponents<T>(moduleType: Type<T>, isSync: boolean):
SyncAsyncResult<ModuleWithComponentFactories<T>> {
const componentPromise = this._compileComponents(moduleType, isSync);
const ngModuleFactory = this._compileModule(moduleType);
const moduleMeta = this._metadataResolver.getNgModuleMetadata(moduleType);
const loadingPromise = this._loadModules(moduleType, isSync);
const createResult = () => {
const componentFactories: ComponentFactory<any>[] = [];
const templates = new Set<CompiledTemplate>();
moduleMeta.transitiveModule.modules.forEach((localModuleMeta) => {
localModuleMeta.declaredDirectives.forEach((dirMeta) => {
if (dirMeta.isComponent) {
const template =
this._createCompiledHostTemplate(dirMeta.type.reference, localModuleMeta);
templates.add(template);
componentFactories.push(template.proxyComponentFactory);
}
});
});
const syncResult = new ModuleWithComponentFactories(ngModuleFactory, componentFactories);
// Note: host components themselves can always be compiled synchronously as they have an
// inline template. However, we still need to wait for the components that they
// reference to be loaded / compiled.
const compile = () => {
templates.forEach((template) => { this._compileTemplate(template); });
return syncResult;
this._compileComponents(moduleType, componentFactories);
return new ModuleWithComponentFactories(this._compileModule(moduleType), componentFactories);
};
const asyncResult = isSync ? Promise.resolve(compile()) : componentPromise.then(compile);
return new SyncAsyncResult(syncResult, asyncResult);
if (isSync) {
return new SyncAsyncResult(createResult());
} else {
return new SyncAsyncResult(null, loadingPromise.then(createResult));
}
}
private _loadModules(mainModule: any, isSync: boolean): Promise<any> {
var loadingPromises: Promise<any>[] = [];
const {ngModule, loading} = this._metadataResolver.loadNgModuleMetadata(mainModule, isSync);
loadingPromises.push(loading);
// Note: the loadingPromise for a module only includes the loading of the exported directives
// of imported modules.
// However, for runtime compilation, we want to transitively compile all modules,
// so we also need to call loadNgModuleMetadata for all nested modules.
ngModule.transitiveModule.modules.forEach((localModuleMeta) => {
loadingPromises.push(
this._metadataResolver.loadNgModuleMetadata(localModuleMeta.type.reference, isSync)
.loading);
});
return Promise.all(loadingPromises);
}
private _compileModule<T>(moduleType: Type<T>): NgModuleFactory<T> {
@ -137,23 +144,30 @@ export class RuntimeCompiler implements Compiler {
/**
* @internal
*/
_compileComponents(mainModule: Type<any>, isSync: boolean): Promise<any> {
const templates = new Set<CompiledTemplate>();
var loadingPromises: Promise<any>[] = [];
_compileComponents(mainModule: Type<any>, allComponentFactories: ComponentFactory<any>[]) {
const ngModule = this._metadataResolver.getNgModuleMetadata(mainModule);
const moduleByDirective = new Map<any, CompileNgModuleMetadata>();
const templates = new Set<CompiledTemplate>();
ngModule.transitiveModule.modules.forEach((localModuleMeta) => {
localModuleMeta.declaredDirectives.forEach((dirMeta) => {
moduleByDirective.set(dirMeta.type.reference, localModuleMeta);
localModuleMeta.declaredDirectives.forEach((dirIdentifier) => {
moduleByDirective.set(dirIdentifier.reference, localModuleMeta);
const dirMeta = this._metadataResolver.getDirectiveMetadata(dirIdentifier.reference);
this._compileDirectiveWrapper(dirMeta, localModuleMeta);
if (dirMeta.isComponent) {
templates.add(this._createCompiledTemplate(dirMeta, localModuleMeta));
if (allComponentFactories) {
const template =
this._createCompiledHostTemplate(dirMeta.type.reference, localModuleMeta);
templates.add(template);
allComponentFactories.push(template.proxyComponentFactory);
}
}
});
});
ngModule.transitiveModule.modules.forEach((localModuleMeta) => {
localModuleMeta.declaredDirectives.forEach((dirMeta) => {
localModuleMeta.declaredDirectives.forEach((dirIdentifier) => {
const dirMeta = this._metadataResolver.getDirectiveMetadata(dirIdentifier.reference);
if (dirMeta.isComponent) {
dirMeta.entryComponents.forEach((entryComponentType) => {
const moduleMeta = moduleByDirective.get(entryComponentType.reference);
@ -167,24 +181,7 @@ export class RuntimeCompiler implements Compiler {
templates.add(this._createCompiledHostTemplate(entryComponentType.reference, moduleMeta));
});
});
templates.forEach((template) => {
if (template.loading) {
if (isSync) {
throw new ComponentStillLoadingError(template.compType.reference);
} else {
loadingPromises.push(template.loading);
}
}
});
const compile =
() => { templates.forEach((template) => { this._compileTemplate(template); }); };
if (isSync) {
compile();
return Promise.resolve(null);
} else {
return Promise.all(loadingPromises).then(compile);
}
templates.forEach((template) => this._compileTemplate(template));
}
clearCacheFor(type: Type<any>) {
@ -193,7 +190,6 @@ export class RuntimeCompiler implements Compiler {
this._compiledHostTemplateCache.delete(type);
var compiledTemplate = this._compiledTemplateCache.get(type);
if (compiledTemplate) {
this._templateNormalizer.clearCacheFor(compiledTemplate.normalizedCompMeta);
this._compiledTemplateCache.delete(type);
}
}
@ -202,7 +198,6 @@ export class RuntimeCompiler implements Compiler {
this._metadataResolver.clearCache();
this._compiledTemplateCache.clear();
this._compiledHostTemplateCache.clear();
this._templateNormalizer.clearCache();
this._compiledNgModuleCache.clear();
}
@ -218,8 +213,7 @@ export class RuntimeCompiler implements Compiler {
assertComponent(compMeta);
var hostMeta = createHostComponentMeta(compMeta);
compiledTemplate = new CompiledTemplate(
true, compMeta.selector, compMeta.type, ngModule, [compMeta],
this._templateNormalizer.normalizeDirective(hostMeta));
true, compMeta.selector, compMeta.type, hostMeta, ngModule, [compMeta.type]);
this._compiledHostTemplateCache.set(compType, compiledTemplate);
}
return compiledTemplate;
@ -231,8 +225,8 @@ export class RuntimeCompiler implements Compiler {
if (!compiledTemplate) {
assertComponent(compMeta);
compiledTemplate = new CompiledTemplate(
false, compMeta.selector, compMeta.type, ngModule, ngModule.transitiveModule.directives,
this._templateNormalizer.normalizeDirective(compMeta));
false, compMeta.selector, compMeta.type, compMeta, ngModule,
ngModule.transitiveModule.directives);
this._compiledTemplateCache.set(compMeta.type.reference, compiledTemplate);
}
return compiledTemplate;
@ -243,16 +237,7 @@ export class RuntimeCompiler implements Compiler {
this._compiledTemplateCache.get(compType);
if (!compiledTemplate) {
throw new Error(
`Illegal state: Compiled view for component ${stringify(compType)} does not exist!`);
}
return compiledTemplate;
}
private _assertComponentLoaded(compType: any, isHost: boolean): CompiledTemplate {
const compiledTemplate = this._assertComponentKnown(compType, isHost);
if (compiledTemplate.loading) {
throw new Error(
`Illegal state: CompiledTemplate for ${stringify(compType)} (isHost: ${isHost}) is still loading!`);
`Illegal state: Compiled view for component ${stringify(compType)} (host: ${isHost}) does not exist!`);
}
return compiledTemplate;
}
@ -285,34 +270,36 @@ export class RuntimeCompiler implements Compiler {
if (template.isCompiled) {
return;
}
const compMeta = template.normalizedCompMeta;
const compMeta = template.compMeta;
const externalStylesheetsByModuleUrl = new Map<string, CompiledStylesheet>();
const stylesCompileResult = this._styleCompiler.compileComponent(compMeta);
stylesCompileResult.externalStylesheets.forEach(
(r) => { externalStylesheetsByModuleUrl.set(r.meta.moduleUrl, r); });
this._resolveStylesCompileResult(
stylesCompileResult.componentStylesheet, externalStylesheetsByModuleUrl);
const viewCompMetas = template.viewComponentTypes.map(
(compType) => this._assertComponentLoaded(compType, false).normalizedCompMeta);
const parsedAnimations = this._animationParser.parseComponent(compMeta);
const directives =
template.directives.map(dir => this._metadataResolver.getDirectiveMetadata(dir.reference));
const pipes = template.ngModule.transitiveModule.pipes.map(
pipe => this._metadataResolver.getPipeMetadata(pipe.reference));
const parsedTemplate = this._templateParser.parse(
compMeta, compMeta.template.template, template.viewDirectives.concat(viewCompMetas),
template.viewPipes, template.schemas, compMeta.type.name);
compMeta, compMeta.template.template, directives, pipes, template.ngModule.schemas,
compMeta.type.name);
const compiledAnimations =
this._animationCompiler.compile(compMeta.type.name, parsedAnimations);
const compileResult = this._viewCompiler.compileComponent(
compMeta, parsedTemplate, ir.variable(stylesCompileResult.componentStylesheet.stylesVar),
template.viewPipes, compiledAnimations);
pipes, compiledAnimations);
compileResult.dependencies.forEach((dep) => {
let depTemplate: CompiledTemplate;
if (dep instanceof ViewClassDependency) {
let vfd = <ViewClassDependency>dep;
depTemplate = this._assertComponentLoaded(vfd.comp.reference, false);
depTemplate = this._assertComponentKnown(vfd.comp.reference, false);
vfd.placeholder.reference = depTemplate.proxyViewClass;
vfd.placeholder.name = `View_${vfd.comp.name}`;
} else if (dep instanceof ComponentFactoryDependency) {
let cfd = <ComponentFactoryDependency>dep;
depTemplate = this._assertComponentLoaded(cfd.comp.reference, true);
depTemplate = this._assertComponentKnown(cfd.comp.reference, true);
cfd.placeholder.reference = depTemplate.proxyComponentFactory;
cfd.placeholder.name = `compFactory_${cfd.comp.name}`;
} else if (dep instanceof DirectiveWrapperDependency) {
@ -361,29 +348,12 @@ class CompiledTemplate {
private _viewClass: Function = null;
proxyViewClass: Type<any>;
proxyComponentFactory: ComponentFactory<any>;
loading: Promise<any> = null;
private _normalizedCompMeta: CompileDirectiveMetadata = null;
isCompiled = false;
isCompiledWithDeps = false;
viewComponentTypes: Type<any>[] = [];
viewDirectives: CompileDirectiveMetadata[] = [];
viewPipes: CompilePipeMetadata[];
schemas: SchemaMetadata[];
constructor(
public isHost: boolean, selector: string, public compType: CompileIdentifierMetadata,
public ngModule: CompileNgModuleMetadata,
viewDirectiveAndComponents: CompileDirectiveMetadata[],
_normalizeResult: SyncAsyncResult<CompileDirectiveMetadata>) {
this.viewPipes = ngModule.transitiveModule.pipes;
this.schemas = ngModule.schemas;
viewDirectiveAndComponents.forEach((dirMeta) => {
if (dirMeta.isComponent) {
this.viewComponentTypes.push(dirMeta.type.reference);
} else {
this.viewDirectives.push(dirMeta);
}
});
public compMeta: CompileDirectiveMetadata, public ngModule: CompileNgModuleMetadata,
public directives: CompileIdentifierMetadata[]) {
const self = this;
this.proxyViewClass = <any>function() {
if (!self._viewClass) {
@ -395,21 +365,6 @@ class CompiledTemplate {
this.proxyComponentFactory = isHost ?
new ComponentFactory<any>(selector, this.proxyViewClass, compType.reference) :
null;
if (_normalizeResult.syncResult) {
this._normalizedCompMeta = _normalizeResult.syncResult;
} else {
this.loading = _normalizeResult.asyncResult.then((normalizedCompMeta) => {
this._normalizedCompMeta = normalizedCompMeta;
this.loading = null;
});
}
}
get normalizedCompMeta(): CompileDirectiveMetadata {
if (this.loading) {
throw new Error(`Template is still loading for ${this.compType.name}!`);
}
return this._normalizedCompMeta;
}
compiled(viewClass: Function) {
@ -417,8 +372,6 @@ class CompiledTemplate {
this.proxyViewClass.prototype = viewClass.prototype;
this.isCompiled = true;
}
depsCompiled() { this.isCompiledWithDeps = true; }
}
function assertComponent(meta: CompileDirectiveMetadata) {

View File

@ -18,79 +18,78 @@ import {AsyncTestCompleter, beforeEach, describe, expect, inject, it} from '@ang
import {SpyResourceLoader} from './spies';
const SOME_MODULE_URL = 'package:some/module/a.js';
const SOME_HTTP_MODULE_URL = 'http://some/module/a.js';
export function main() {
describe('DirectiveNormalizer', () => {
var dirType: CompileTypeMetadata;
var dirTypeWithHttpUrl: CompileTypeMetadata;
beforeEach(() => { TestBed.configureCompiler({providers: TEST_COMPILER_PROVIDERS}); });
beforeEach(() => {
dirType = new CompileTypeMetadata({moduleUrl: 'package:some/module/a.js', name: 'SomeComp'});
dirTypeWithHttpUrl =
new CompileTypeMetadata({moduleUrl: 'http://some/module/a.js', name: 'SomeComp'});
});
describe('normalizeDirective', () => {
it('should throw if no template was specified',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
expect(() => normalizer.normalizeDirective(new CompileDirectiveMetadata({
type: dirType,
isComponent: true,
template:
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []})
}))).toThrowError('No template specified for component SomeComp');
expect(() => normalizer.normalizeTemplate({
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
})).toThrowError('No template specified for component SomeComp');
}));
});
describe('normalizeTemplateSync', () => {
it('should store the template',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
let template = normalizer.normalizeTemplateSync(dirType, new CompileTemplateMetadata({
let template = normalizer.normalizeTemplateSync({
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null,
template: 'a',
templateUrl: null,
styles: [],
styleUrls: []
}));
});
expect(template.template).toEqual('a');
expect(template.templateUrl).toEqual('package:some/module/a.js');
}));
it('should resolve styles on the annotation against the moduleUrl',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
let template = normalizer.normalizeTemplateSync(dirType, new CompileTemplateMetadata({
let template = normalizer.normalizeTemplateSync({
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null,
template: '',
templateUrl: null,
styles: [],
styleUrls: ['test.css']
}));
});
expect(template.styleUrls).toEqual(['package:some/module/test.css']);
}));
it('should resolve styles in the template against the moduleUrl',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
let template =
normalizer.normalizeTemplateSync(dirType, new CompileTemplateMetadata({
let template = normalizer.normalizeTemplateSync({
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null,
template: '<style>@import test.css</style>',
templateUrl: null,
styles: [],
styleUrls: []
}));
});
expect(template.styleUrls).toEqual(['package:some/module/test.css']);
}));
it('should use ViewEncapsulation.Emulated by default',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
let template = normalizer.normalizeTemplateSync(dirType, new CompileTemplateMetadata({
let template = normalizer.normalizeTemplateSync({
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null,
template: '',
templateUrl: null,
styles: [],
styleUrls: ['test.css']
}));
});
expect(template.encapsulation).toEqual(ViewEncapsulation.Emulated);
}));
@ -99,14 +98,15 @@ export function main() {
[CompilerConfig, DirectiveNormalizer],
(config: CompilerConfig, normalizer: DirectiveNormalizer) => {
config.defaultEncapsulation = ViewEncapsulation.None;
let template =
normalizer.normalizeTemplateSync(dirType, new CompileTemplateMetadata({
let template = normalizer.normalizeTemplateSync({
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null,
template: '',
templateUrl: null,
styles: [],
styleUrls: ['test.css']
}));
});
expect(template.encapsulation).toEqual(ViewEncapsulation.None);
}));
});
@ -120,13 +120,15 @@ export function main() {
resourceLoader: MockResourceLoader) => {
resourceLoader.expect('package:some/module/sometplurl.html', 'a');
normalizer
.normalizeTemplateAsync(dirType, new CompileTemplateMetadata({
.normalizeTemplateAsync({
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null,
template: null,
templateUrl: 'sometplurl.html',
styles: [],
styleUrls: ['test.css']
}))
})
.then((template: CompileTemplateMetadata) => {
expect(template.template).toEqual('a');
expect(template.templateUrl).toEqual('package:some/module/sometplurl.html');
@ -142,13 +144,15 @@ export function main() {
resourceLoader: MockResourceLoader) => {
resourceLoader.expect('package:some/module/tpl/sometplurl.html', '');
normalizer
.normalizeTemplateAsync(dirType, new CompileTemplateMetadata({
.normalizeTemplateAsync({
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null,
template: null,
templateUrl: 'tpl/sometplurl.html',
styles: [],
styleUrls: ['test.css']
}))
})
.then((template: CompileTemplateMetadata) => {
expect(template.styleUrls).toEqual(['package:some/module/test.css']);
async.done();
@ -164,13 +168,15 @@ export function main() {
resourceLoader.expect(
'package:some/module/tpl/sometplurl.html', '<style>@import test.css</style>');
normalizer
.normalizeTemplateAsync(dirType, new CompileTemplateMetadata({
.normalizeTemplateAsync({
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null,
template: null,
templateUrl: 'tpl/sometplurl.html',
styles: [],
styleUrls: []
}))
})
.then((template: CompileTemplateMetadata) => {
expect(template.styleUrls).toEqual(['package:some/module/tpl/test.css']);
async.done();
@ -249,13 +255,15 @@ export function main() {
(async: AsyncTestCompleter, normalizer: DirectiveNormalizer,
resourceLoader: MockResourceLoader) => {
resourceLoader.expect('package:some/module/cmp.html', 'a');
var templateMeta = new CompileTemplateMetadata({
var prenormMeta = {
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
templateUrl: 'cmp.html',
});
};
Promise
.all([
normalizer.normalizeTemplateAsync(dirType, templateMeta),
normalizer.normalizeTemplateAsync(dirType, templateMeta)
normalizer.normalizeTemplateAsync(prenormMeta),
normalizer.normalizeTemplateAsync(prenormMeta)
])
.then((templates: CompileTemplateMetadata[]) => {
expect(templates[0].template).toEqual('a');
@ -273,8 +281,13 @@ export function main() {
var viewEncapsulation = ViewEncapsulation.Native;
var template = normalizer.normalizeLoadedTemplate(
dirType, new CompileTemplateMetadata(
{encapsulation: viewEncapsulation, styles: [], styleUrls: []}),
{
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: viewEncapsulation,
styles: [],
styleUrls: []
},
'', 'package:some/module/');
expect(template.encapsulation).toBe(viewEncapsulation);
}));
@ -282,17 +295,27 @@ export function main() {
it('should keep the template as html',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate(
dirType,
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}), 'a',
'package:some/module/');
{
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null,
styles: [],
styleUrls: []
},
'a', 'package:some/module/');
expect(template.template).toEqual('a');
}));
it('should collect ngContent',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate(
dirType,
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
{
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null,
styles: [],
styleUrls: []
},
'<ng-content select="a"></ng-content>', 'package:some/module/');
expect(template.ngContentSelectors).toEqual(['a']);
}));
@ -300,8 +323,13 @@ export function main() {
it('should normalize ngContent wildcard selector',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate(
dirType,
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
{
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null,
styles: [],
styleUrls: []
},
'<ng-content></ng-content><ng-content select></ng-content><ng-content select="*"></ng-content>',
'package:some/module/');
expect(template.ngContentSelectors).toEqual(['*', '*', '*']);
@ -310,8 +338,13 @@ export function main() {
it('should collect top level styles in the template',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate(
dirType,
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
{
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null,
styles: [],
styleUrls: []
},
'<style>a</style>', 'package:some/module/');
expect(template.styles).toEqual(['a']);
}));
@ -319,8 +352,13 @@ export function main() {
it('should collect styles inside in elements',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate(
dirType,
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
{
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null,
styles: [],
styleUrls: []
},
'<div><style>a</style></div>', 'package:some/module/');
expect(template.styles).toEqual(['a']);
}));
@ -328,8 +366,13 @@ export function main() {
it('should collect styleUrls in the template',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate(
dirType,
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
{
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null,
styles: [],
styleUrls: []
},
'<link rel="stylesheet" href="aUrl">', 'package:some/module/');
expect(template.styleUrls).toEqual(['package:some/module/aUrl']);
}));
@ -337,8 +380,13 @@ export function main() {
it('should collect styleUrls in elements',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate(
dirType,
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
{
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null,
styles: [],
styleUrls: []
},
'<div><link rel="stylesheet" href="aUrl"></div>', 'package:some/module/');
expect(template.styleUrls).toEqual(['package:some/module/aUrl']);
}));
@ -346,8 +394,13 @@ export function main() {
it('should ignore link elements with non stylesheet rel attribute',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate(
dirType,
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
{
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null,
styles: [],
styleUrls: []
},
'<link href="b" rel="a">', 'package:some/module/');
expect(template.styleUrls).toEqual([]);
}));
@ -355,8 +408,13 @@ export function main() {
it('should ignore link elements with absolute urls but non package: scheme',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate(
dirType,
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
{
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null,
styles: [],
styleUrls: []
},
'<link href="http://some/external.css" rel="stylesheet">', 'package:some/module/');
expect(template.styleUrls).toEqual([]);
}));
@ -364,8 +422,13 @@ export function main() {
it('should extract @import style urls into styleAbsUrl',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate(
dirType, new CompileTemplateMetadata(
{encapsulation: null, styles: ['@import "test.css";'], styleUrls: []}),
{
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null,
styles: ['@import "test.css";'],
styleUrls: []
},
'', 'package:some/module/id');
expect(template.styles).toEqual(['']);
expect(template.styleUrls).toEqual(['package:some/module/test.css']);
@ -374,11 +437,13 @@ export function main() {
it('should not resolve relative urls in inline styles',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate(
dirType, new CompileTemplateMetadata({
{
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null,
styles: ['.foo{background-image: url(\'double.jpg\');'],
styleUrls: []
}),
},
'', 'package:some/module/id');
expect(template.styles).toEqual(['.foo{background-image: url(\'double.jpg\');']);
}));
@ -386,8 +451,13 @@ export function main() {
it('should resolve relative style urls in styleUrls',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate(
dirType, new CompileTemplateMetadata(
{encapsulation: null, styles: [], styleUrls: ['test.css']}),
{
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null,
styles: [],
styleUrls: ['test.css']
},
'', 'package:some/module/id');
expect(template.styles).toEqual([]);
expect(template.styleUrls).toEqual(['package:some/module/test.css']);
@ -396,8 +466,13 @@ export function main() {
it('should resolve relative style urls in styleUrls with http directive url',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate(
dirTypeWithHttpUrl, new CompileTemplateMetadata(
{encapsulation: null, styles: [], styleUrls: ['test.css']}),
{
componentType: SomeComp,
moduleUrl: SOME_HTTP_MODULE_URL,
encapsulation: null,
styles: [],
styleUrls: ['test.css']
},
'', 'http://some/module/id');
expect(template.styles).toEqual([]);
expect(template.styleUrls).toEqual(['http://some/module/test.css']);
@ -406,8 +481,13 @@ export function main() {
it('should normalize ViewEncapsulation.Emulated to ViewEncapsulation.None if there are no styles nor stylesheets',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate(
dirType, new CompileTemplateMetadata(
{encapsulation: ViewEncapsulation.Emulated, styles: [], styleUrls: []}),
{
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: ViewEncapsulation.Emulated,
styles: [],
styleUrls: []
},
'', 'package:some/module/id');
expect(template.encapsulation).toEqual(ViewEncapsulation.None);
}));
@ -415,8 +495,13 @@ export function main() {
it('should ignore ng-content in elements with ngNonBindable',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate(
dirType,
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
{
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null,
styles: [],
styleUrls: []
},
'<div ngNonBindable><ng-content select="a"></ng-content></div>',
'package:some/module/');
expect(template.ngContentSelectors).toEqual([]);
@ -425,8 +510,13 @@ export function main() {
it('should still collect <style> in elements with ngNonBindable',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate(
dirType,
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
{
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null,
styles: [],
styleUrls: []
},
'<div ngNonBindable><style>div {color:red}</style></div>', 'package:some/module/');
expect(template.styles).toEqual(['div {color:red}']);
}));
@ -444,3 +534,5 @@ function programResourceLoaderSpy(spy: SpyResourceLoader, results: {[key: string
}
});
}
class SomeComp {}

View File

@ -9,10 +9,12 @@
import {TEST_COMPILER_PROVIDERS} from '@angular/compiler/testing/test_bindings';
import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, ChangeDetectionStrategy, Component, DoCheck, Injectable, NgModule, OnChanges, OnDestroy, OnInit, Pipe, SimpleChanges, ViewEncapsulation} from '@angular/core';
import {LIFECYCLE_HOOKS_VALUES} from '@angular/core/src/metadata/lifecycle_hooks';
import {TestBed, inject} from '@angular/core/testing';
import {TestBed, async, inject} from '@angular/core/testing';
import {stringify} from '../src/facade/lang';
import {CompileMetadataResolver} from '../src/metadata_resolver';
import {ResourceLoader} from '../src/resource_loader';
import {MockResourceLoader} from '../testing/resource_loader_mock';
import {MalformedStylesComponent} from './metadata_resolver_fixture';
@ -20,15 +22,35 @@ export function main() {
describe('CompileMetadataResolver', () => {
beforeEach(() => { TestBed.configureCompiler({providers: TEST_COMPILER_PROVIDERS}); });
describe('getDirectiveMetadata', () => {
it('should read metadata',
it('should throw on the get... methods if the module has not been loaded yet',
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
const meta = resolver.getDirectiveMetadata(ComponentWithEverything);
@NgModule({})
class SomeModule {
}
@Pipe({name: 'pipe'})
class SomePipe {
}
expect(() => resolver.getNgModuleMetadata(SomeModule)).toThrowError(/Illegal state/);
expect(() => resolver.getDirectiveMetadata(ComponentWithEverythingInline))
.toThrowError(/Illegal state/);
expect(() => resolver.getPipeMetadata(SomePipe)).toThrowError(/Illegal state/);
}));
it('should read metadata in sync for components with inline resources',
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
@NgModule({declarations: [ComponentWithEverythingInline]})
class SomeModule {
}
resolver.loadNgModuleMetadata(SomeModule, true);
const meta = resolver.getDirectiveMetadata(ComponentWithEverythingInline);
expect(meta.selector).toEqual('someSelector');
expect(meta.exportAs).toEqual('someExportAs');
expect(meta.isComponent).toBe(true);
expect(meta.type.reference).toBe(ComponentWithEverything);
expect(meta.type.name).toEqual(stringify(ComponentWithEverything));
expect(meta.type.reference).toBe(ComponentWithEverythingInline);
expect(meta.type.name).toEqual(stringify(ComponentWithEverythingInline));
expect(meta.type.lifecycleHooks).toEqual(LIFECYCLE_HOOKS_VALUES);
expect(meta.changeDetection).toBe(ChangeDetectionStrategy.Default);
expect(meta.inputs).toEqual({'someProp': 'someProp'});
@ -38,14 +60,67 @@ export function main() {
expect(meta.hostAttributes).toEqual({'someHostAttr': 'someHostAttrValue'});
expect(meta.template.encapsulation).toBe(ViewEncapsulation.Emulated);
expect(meta.template.styles).toEqual(['someStyle']);
expect(meta.template.styleUrls).toEqual(['someStyleUrl']);
expect(meta.template.template).toEqual('someTemplate');
expect(meta.template.templateUrl).toEqual('someTemplateUrl');
expect(meta.template.interpolation).toEqual(['{{', '}}']);
}));
it('should throw when reading metadata for component with external resources when sync=true is passed',
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
@NgModule({declarations: [ComponentWithExternalResources]})
class SomeModule {
}
expect(() => resolver.loadNgModuleMetadata(SomeModule, true))
.toThrowError(
`Can't compile synchronously as ${stringify(ComponentWithExternalResources)} is still being loaded!`);
}));
it('should read external metadata when sync=false',
async(inject(
[CompileMetadataResolver, ResourceLoader],
(resolver: CompileMetadataResolver, resourceLoader: MockResourceLoader) => {
@NgModule({declarations: [ComponentWithExternalResources]})
class SomeModule {
}
resourceLoader.when('someTemplateUrl', 'someTemplate');
resolver.loadNgModuleMetadata(SomeModule, false).loading.then(() => {
const meta = resolver.getDirectiveMetadata(ComponentWithExternalResources);
expect(meta.selector).toEqual('someSelector');
expect(meta.template.styleUrls).toEqual(['someStyleUrl']);
expect(meta.template.templateUrl).toEqual('someTemplateUrl');
expect(meta.template.template).toEqual('someTemplate');
});
resourceLoader.flush();
})));
it('should wait for external resources of imported modules',
async(inject(
[CompileMetadataResolver, ResourceLoader],
(resolver: CompileMetadataResolver, resourceLoader: MockResourceLoader) => {
@NgModule({declarations: [ComponentWithExternalResources]})
class SomeImportedModule {
}
@NgModule({imports: [SomeImportedModule]})
class SomeModule {
}
resourceLoader.when('someTemplateUrl', 'someTemplate');
resolver.loadNgModuleMetadata(SomeModule, false).loading.then(() => {
const meta = resolver.getDirectiveMetadata(ComponentWithExternalResources);
expect(meta.selector).toEqual('someSelector');
});
resourceLoader.flush();
})));
it('should use the moduleUrl from the reflector if none is given',
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
@NgModule({declarations: [ComponentWithoutModuleId]})
class SomeModule {
}
resolver.loadNgModuleMetadata(SomeModule, true);
const value: string =
resolver.getDirectiveMetadata(ComponentWithoutModuleId).type.moduleUrl;
const expectedEndValue = './ComponentWithoutModuleId';
@ -54,7 +129,11 @@ export function main() {
it('should throw when the moduleId is not a string',
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
expect(() => resolver.getDirectiveMetadata(ComponentWithInvalidModuleId))
@NgModule({declarations: [ComponentWithInvalidModuleId]})
class SomeModule {
}
expect(() => resolver.loadNgModuleMetadata(SomeModule, true))
.toThrowError(
`moduleId should be a string in "ComponentWithInvalidModuleId". See` +
` https://goo.gl/wIDDiL for more information.\n` +
@ -65,13 +144,21 @@ export function main() {
it('should throw when metadata is incorrectly typed',
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
expect(() => resolver.getDirectiveMetadata(MalformedStylesComponent))
@NgModule({declarations: [MalformedStylesComponent]})
class SomeModule {
}
expect(() => resolver.loadNgModuleMetadata(SomeModule, true))
.toThrowError(`Expected 'styles' to be an array of strings.`);
}));
it('should throw with descriptive error message when provider token can not be resolved',
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
expect(() => resolver.getDirectiveMetadata(MyBrokenComp1))
@NgModule({declarations: [MyBrokenComp1]})
class SomeModule {
}
expect(() => resolver.loadNgModuleMetadata(SomeModule, true))
.toThrowError(`Can't resolve all parameters for MyBrokenComp1: (?).`);
}));
it('should throw with descriptive error message when a directive is passed to imports',
@ -79,7 +166,7 @@ export function main() {
@NgModule({imports: [ComponentWithoutModuleId]})
class ModuleWithImportedComponent {
}
expect(() => resolver.getNgModuleMetadata(ModuleWithImportedComponent))
expect(() => resolver.loadNgModuleMetadata(ModuleWithImportedComponent, true))
.toThrowError(
`Unexpected directive 'ComponentWithoutModuleId' imported by the module 'ModuleWithImportedComponent'`);
}));
@ -92,7 +179,7 @@ export function main() {
@NgModule({imports: [SomePipe]})
class ModuleWithImportedPipe {
}
expect(() => resolver.getNgModuleMetadata(ModuleWithImportedPipe))
expect(() => resolver.loadNgModuleMetadata(ModuleWithImportedPipe, true))
.toThrowError(
`Unexpected pipe 'SomePipe' imported by the module 'ModuleWithImportedPipe'`);
}));
@ -105,7 +192,7 @@ export function main() {
@NgModule({declarations: [SomeModule]})
class ModuleWithDeclaredModule {
}
expect(() => resolver.getNgModuleMetadata(ModuleWithDeclaredModule))
expect(() => resolver.loadNgModuleMetadata(ModuleWithDeclaredModule, true))
.toThrowError(
`Unexpected module 'SomeModule' declared by the module 'ModuleWithDeclaredModule'`);
}));
@ -115,7 +202,7 @@ export function main() {
@NgModule({declarations: [null]})
class ModuleWithNullDeclared {
}
expect(() => resolver.getNgModuleMetadata(ModuleWithNullDeclared))
expect(() => resolver.loadNgModuleMetadata(ModuleWithNullDeclared, true))
.toThrowError(
`Unexpected value 'null' declared by the module 'ModuleWithNullDeclared'`);
}));
@ -125,7 +212,7 @@ export function main() {
@NgModule({imports: [null]})
class ModuleWithNullImported {
}
expect(() => resolver.getNgModuleMetadata(ModuleWithNullImported))
expect(() => resolver.loadNgModuleMetadata(ModuleWithNullImported, true))
.toThrowError(
`Unexpected value 'null' imported by the module 'ModuleWithNullImported'`);
}));
@ -133,20 +220,32 @@ export function main() {
it('should throw with descriptive error message when a param token of a dependency is undefined',
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
expect(() => resolver.getDirectiveMetadata(MyBrokenComp2))
@NgModule({declarations: [MyBrokenComp2]})
class SomeModule {
}
expect(() => resolver.loadNgModuleMetadata(SomeModule, true))
.toThrowError(`Can't resolve all parameters for NonAnnotatedService: (?).`);
}));
it('should throw with descriptive error message when one of providers is not present',
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
expect(() => resolver.getDirectiveMetadata(MyBrokenComp3))
@NgModule({declarations: [MyBrokenComp3]})
class SomeModule {
}
expect(() => resolver.loadNgModuleMetadata(SomeModule, true))
.toThrowError(
`Invalid providers for "MyBrokenComp3" - only instances of Provider and Type are allowed, got: [SimpleService, ?null?, ...]`);
}));
it('should throw with descriptive error message when one of viewProviders is not present',
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
expect(() => resolver.getDirectiveMetadata(MyBrokenComp4))
@NgModule({declarations: [MyBrokenComp4]})
class SomeModule {
}
expect(() => resolver.loadNgModuleMetadata(SomeModule, true))
.toThrowError(
`Invalid viewProviders for "MyBrokenComp4" - only instances of Provider and Type are allowed, got: [?null?, ...]`);
}));
@ -160,25 +259,49 @@ export function main() {
class ModuleWithUndefinedBootstrap {
}
expect(() => resolver.getNgModuleMetadata(ModuleWithNullBootstrap))
expect(() => resolver.loadNgModuleMetadata(ModuleWithNullBootstrap, true))
.toThrowError(
`Unexpected value 'null' used in the bootstrap property of module 'ModuleWithNullBootstrap'`);
expect(() => resolver.getNgModuleMetadata(ModuleWithUndefinedBootstrap))
expect(() => resolver.loadNgModuleMetadata(ModuleWithUndefinedBootstrap, true))
.toThrowError(
`Unexpected value 'undefined' used in the bootstrap property of module 'ModuleWithUndefinedBootstrap'`);
}));
it('should throw an error when the interpolation config has invalid symbols',
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
expect(() => resolver.getDirectiveMetadata(ComponentWithInvalidInterpolation1))
@NgModule({declarations: [ComponentWithInvalidInterpolation1]})
class Module1 {
}
expect(() => resolver.loadNgModuleMetadata(Module1, true))
.toThrowError(`[' ', ' '] contains unusable interpolation symbol.`);
expect(() => resolver.getDirectiveMetadata(ComponentWithInvalidInterpolation2))
@NgModule({declarations: [ComponentWithInvalidInterpolation2]})
class Module2 {
}
expect(() => resolver.loadNgModuleMetadata(Module2, true))
.toThrowError(`['{', '}'] contains unusable interpolation symbol.`);
expect(() => resolver.getDirectiveMetadata(ComponentWithInvalidInterpolation3))
@NgModule({declarations: [ComponentWithInvalidInterpolation3]})
class Module3 {
}
expect(() => resolver.loadNgModuleMetadata(Module3, true))
.toThrowError(`['<%', '%>'] contains unusable interpolation symbol.`);
expect(() => resolver.getDirectiveMetadata(ComponentWithInvalidInterpolation4))
@NgModule({declarations: [ComponentWithInvalidInterpolation4]})
class Module4 {
}
expect(() => resolver.loadNgModuleMetadata(Module4, true))
.toThrowError(`['&#', '}}'] contains unusable interpolation symbol.`);
expect(() => resolver.getDirectiveMetadata(ComponentWithInvalidInterpolation5))
@NgModule({declarations: [ComponentWithInvalidInterpolation5]})
class Module5 {
}
expect(() => resolver.loadNgModuleMetadata(Module5, true))
.toThrowError(`['&lbrace;', '}}'] contains unusable interpolation symbol.`);
}));
});
@ -194,12 +317,10 @@ export function main() {
class MyModule {
}
const modMeta = resolver.getNgModuleMetadata(MyModule);
const modMeta = resolver.loadNgModuleMetadata(MyModule, true).ngModule;
expect(modMeta.declaredDirectives.length).toBe(1);
expect(modMeta.declaredDirectives[0].type.reference).toBe(MyComp);
expect(modMeta.declaredDirectives[0].reference).toBe(MyComp);
}));
});
}
@Component({selector: 'someComponent', template: ''})
@ -210,6 +331,14 @@ class ComponentWithoutModuleId {
class ComponentWithInvalidModuleId {
}
@Component({
selector: 'someSelector',
templateUrl: 'someTemplateUrl',
styleUrls: ['someStyleUrl'],
})
class ComponentWithExternalResources {
}
@Component({
selector: 'someSelector',
inputs: ['someProp'],
@ -223,13 +352,11 @@ class ComponentWithInvalidModuleId {
moduleId: 'someModuleId',
changeDetection: ChangeDetectionStrategy.Default,
template: 'someTemplate',
templateUrl: 'someTemplateUrl',
encapsulation: ViewEncapsulation.Emulated,
styles: ['someStyle'],
styleUrls: ['someStyleUrl'],
interpolation: ['{{', '}}']
})
class ComponentWithEverything implements OnChanges,
class ComponentWithEverythingInline implements OnChanges,
OnInit, DoCheck, OnDestroy, AfterContentInit, AfterContentChecked, AfterViewInit,
AfterViewChecked {
ngOnChanges(changes: SimpleChanges): void {}

View File

@ -8,8 +8,7 @@
import {DirectiveResolver, ResourceLoader} from '@angular/compiler';
import {Compiler, Component, Injector, NgModule, NgModuleFactory} from '@angular/core';
import {TestBed, async, fakeAsync, tick} from '@angular/core/testing';
import {beforeEach, describe, inject, it} from '@angular/core/testing/testing_internal';
import {TestBed, async, fakeAsync, inject, tick} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/matchers';
import {stringify} from '../src/facade/lang';

View File

@ -11,7 +11,7 @@ import {ApplicationRef, destroyPlatform} from '@angular/core/src/application_ref
import {Console} from '@angular/core/src/console';
import {ComponentRef} from '@angular/core/src/linker/component_factory';
import {Testability, TestabilityRegistry} from '@angular/core/src/testability/testability';
import {AsyncTestCompleter, Log, afterEach, beforeEach, beforeEachProviders, describe, inject, it} from '@angular/core/testing/testing_internal';
import {AsyncTestCompleter, Log, afterEach, beforeEach, beforeEachProviders, describe, iit, inject, it} from '@angular/core/testing/testing_internal';
import {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
@ -149,11 +149,19 @@ export function main() {
afterEach(destroyPlatform);
it('should throw if bootstrapped Directive is not a Component', () => {
expect(() => bootstrap(HelloRootDirectiveIsNotCmp))
.toThrowError(
it('should throw if bootstrapped Directive is not a Component',
inject([AsyncTestCompleter], (done: AsyncTestCompleter) => {
var logger = new MockConsole();
var errorHandler = new ErrorHandler(false);
errorHandler._console = logger as any;
bootstrap(HelloRootDirectiveIsNotCmp, [
{provide: ErrorHandler, useValue: errorHandler}
]).catch((e) => {
expect(e.message).toBe(
`Could not compile '${stringify(HelloRootDirectiveIsNotCmp)}' because it is not a component.`);
done.done();
});
}));
it('should throw if no element is found',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {