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( const resolver = new compiler.CompileMetadataResolver(
new compiler.NgModuleResolver(staticReflector), new compiler.NgModuleResolver(staticReflector),
new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector), new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector),
elementSchemaRegistry, staticReflector); elementSchemaRegistry, normalizer, staticReflector);
// TODO(vicb): do not pass cliOptions.i18nFormat here // TODO(vicb): do not pass cliOptions.i18nFormat here
const offlineCompiler = new compiler.OfflineCompiler( 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.ViewCompiler(config, elementSchemaRegistry),
new compiler.DirectiveWrapperCompiler( new compiler.DirectiveWrapperCompiler(
config, expressionParser, elementSchemaRegistry, console), config, expressionParser, elementSchemaRegistry, console),

View File

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

View File

@ -326,9 +326,7 @@ export class CompileDirectiveMetadata implements CompileMetadataWithIdentifier {
Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>, Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>,
queries?: CompileQueryMetadata[], queries?: CompileQueryMetadata[],
viewQueries?: CompileQueryMetadata[], viewQueries?: CompileQueryMetadata[],
entryComponents?: CompileTypeMetadata[], entryComponents?: CompileIdentifierMetadata[],
viewDirectives?: CompileTypeMetadata[],
viewPipes?: CompileTypeMetadata[],
template?: CompileTemplateMetadata template?: CompileTemplateMetadata
} = {}): CompileDirectiveMetadata { } = {}): CompileDirectiveMetadata {
var hostListeners: {[key: string]: string} = {}; var hostListeners: {[key: string]: string} = {};
@ -397,7 +395,7 @@ export class CompileDirectiveMetadata implements CompileMetadataWithIdentifier {
queries: CompileQueryMetadata[]; queries: CompileQueryMetadata[];
viewQueries: CompileQueryMetadata[]; viewQueries: CompileQueryMetadata[];
// Note: Need to keep types here to prevent cycles! // Note: Need to keep types here to prevent cycles!
entryComponents: CompileTypeMetadata[]; entryComponents: CompileIdentifierMetadata[];
template: CompileTemplateMetadata; template: CompileTemplateMetadata;
@ -421,9 +419,7 @@ export class CompileDirectiveMetadata implements CompileMetadataWithIdentifier {
Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>, Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>,
queries?: CompileQueryMetadata[], queries?: CompileQueryMetadata[],
viewQueries?: CompileQueryMetadata[], viewQueries?: CompileQueryMetadata[],
entryComponents?: CompileTypeMetadata[], entryComponents?: CompileIdentifierMetadata[],
viewDirectives?: CompileTypeMetadata[],
viewPipes?: CompileTypeMetadata[],
template?: CompileTemplateMetadata, template?: CompileTemplateMetadata,
} = {}) { } = {}) {
this.type = type; this.type = type;
@ -506,13 +502,13 @@ export class CompilePipeMetadata implements CompileMetadataWithIdentifier {
*/ */
export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier { export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier {
type: CompileTypeMetadata; type: CompileTypeMetadata;
declaredDirectives: CompileDirectiveMetadata[]; declaredDirectives: CompileIdentifierMetadata[];
exportedDirectives: CompileDirectiveMetadata[]; exportedDirectives: CompileIdentifierMetadata[];
declaredPipes: CompilePipeMetadata[]; declaredPipes: CompileIdentifierMetadata[];
exportedPipes: CompilePipeMetadata[]; exportedPipes: CompileIdentifierMetadata[];
// Note: See CompileDirectiveMetadata.entryComponents why this has to be a type. // Note: See CompileDirectiveMetadata.entryComponents why this has to be a type.
entryComponents: CompileTypeMetadata[]; entryComponents: CompileIdentifierMetadata[];
bootstrapComponents: CompileTypeMetadata[]; bootstrapComponents: CompileIdentifierMetadata[];
providers: CompileProviderMetadata[]; providers: CompileProviderMetadata[];
importedModules: CompileNgModuleMetadata[]; importedModules: CompileNgModuleMetadata[];
@ -529,12 +525,12 @@ export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier {
type?: CompileTypeMetadata, type?: CompileTypeMetadata,
providers?: providers?:
Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>, Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>,
declaredDirectives?: CompileDirectiveMetadata[], declaredDirectives?: CompileIdentifierMetadata[],
exportedDirectives?: CompileDirectiveMetadata[], exportedDirectives?: CompileIdentifierMetadata[],
declaredPipes?: CompilePipeMetadata[], declaredPipes?: CompileIdentifierMetadata[],
exportedPipes?: CompilePipeMetadata[], exportedPipes?: CompileIdentifierMetadata[],
entryComponents?: CompileTypeMetadata[], entryComponents?: CompileIdentifierMetadata[],
bootstrapComponents?: CompileTypeMetadata[], bootstrapComponents?: CompileIdentifierMetadata[],
importedModules?: CompileNgModuleMetadata[], importedModules?: CompileNgModuleMetadata[],
exportedModules?: CompileNgModuleMetadata[], exportedModules?: CompileNgModuleMetadata[],
transitiveModule?: TransitiveCompileNgModuleMetadata, transitiveModule?: TransitiveCompileNgModuleMetadata,
@ -560,15 +556,16 @@ export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier {
} }
export class TransitiveCompileNgModuleMetadata { export class TransitiveCompileNgModuleMetadata {
directivesSet = new Set<Type<any>>(); directivesSet = new Set<any>();
pipesSet = new Set<Type<any>>(); pipesSet = new Set<any>();
constructor( constructor(
public modules: CompileNgModuleMetadata[], public providers: CompileProviderMetadata[], public modules: CompileNgModuleMetadata[], public providers: CompileProviderMetadata[],
public entryComponents: CompileTypeMetadata[], public directives: CompileDirectiveMetadata[], public entryComponents: CompileIdentifierMetadata[],
public pipes: CompilePipeMetadata[]) { public directives: CompileIdentifierMetadata[], public pipes: CompileIdentifierMetadata[],
directives.forEach(dir => this.directivesSet.add(dir.type.reference)); public loadingPromises: Promise<any>[]) {
pipes.forEach(pipe => this.pipesSet.add(pipe.type.reference)); 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 * 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 {CompilerConfig} from './config';
import {isBlank, isPresent} from './facade/lang'; import {isBlank, isPresent, stringify} from './facade/lang';
import * as html from './ml_parser/ast'; import * as html from './ml_parser/ast';
import {HtmlParser} from './ml_parser/html_parser'; import {HtmlParser} from './ml_parser/html_parser';
import {InterpolationConfig} from './ml_parser/interpolation_config'; import {InterpolationConfig} from './ml_parser/interpolation_config';
@ -20,6 +20,18 @@ import {PreparsedElementType, preparseElement} from './template_parser/template_
import {UrlResolver} from './url_resolver'; import {UrlResolver} from './url_resolver';
import {SyncAsyncResult} from './util'; 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() @Injectable()
export class DirectiveNormalizer { export class DirectiveNormalizer {
private _resourceLoaderCache = new Map<string, Promise<string>>(); private _resourceLoaderCache = new Map<string, Promise<string>>();
@ -48,65 +60,56 @@ export class DirectiveNormalizer {
return result; return result;
} }
normalizeDirective(directive: CompileDirectiveMetadata): normalizeTemplate(prenormData: PrenormalizedTemplateMetadata):
SyncAsyncResult<CompileDirectiveMetadata> { SyncAsyncResult<CompileTemplateMetadata> {
if (!directive.isComponent) {
// For non components there is nothing to be normalized yet.
return new SyncAsyncResult(directive, Promise.resolve(directive));
}
let normalizedTemplateSync: CompileTemplateMetadata = null; let normalizedTemplateSync: CompileTemplateMetadata = null;
let normalizedTemplateAsync: Promise<CompileTemplateMetadata>; let normalizedTemplateAsync: Promise<CompileTemplateMetadata>;
if (isPresent(directive.template.template)) { if (isPresent(prenormData.template)) {
normalizedTemplateSync = this.normalizeTemplateSync(directive.type, directive.template); normalizedTemplateSync = this.normalizeTemplateSync(prenormData);
normalizedTemplateAsync = Promise.resolve(normalizedTemplateSync); normalizedTemplateAsync = Promise.resolve(normalizedTemplateSync);
} else if (directive.template.templateUrl) { } else if (prenormData.templateUrl) {
normalizedTemplateAsync = this.normalizeTemplateAsync(directive.type, directive.template); normalizedTemplateAsync = this.normalizeTemplateAsync(prenormData);
} else { } 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) { if (normalizedTemplateSync && normalizedTemplateSync.styleUrls.length === 0) {
// sync case // sync case
let normalizedDirective = _cloneDirectiveWithTemplate(directive, normalizedTemplateSync); return new SyncAsyncResult(normalizedTemplateSync);
return new SyncAsyncResult(normalizedDirective, Promise.resolve(normalizedDirective));
} else { } else {
// async case // async case
return new SyncAsyncResult( return new SyncAsyncResult(
null, null, normalizedTemplateAsync.then(
normalizedTemplateAsync (normalizedTemplate) => this.normalizeExternalStylesheets(normalizedTemplate)));
.then((normalizedTemplate) => this.normalizeExternalStylesheets(normalizedTemplate))
.then(
(normalizedTemplate) =>
_cloneDirectiveWithTemplate(directive, normalizedTemplate)));
} }
} }
normalizeTemplateSync(directiveType: CompileTypeMetadata, template: CompileTemplateMetadata): normalizeTemplateSync(prenomData: PrenormalizedTemplateMetadata): CompileTemplateMetadata {
CompileTemplateMetadata { return this.normalizeLoadedTemplate(prenomData, prenomData.template, prenomData.moduleUrl);
return this.normalizeLoadedTemplate(
directiveType, template, template.template, directiveType.moduleUrl);
} }
normalizeTemplateAsync(directiveType: CompileTypeMetadata, template: CompileTemplateMetadata): normalizeTemplateAsync(prenomData: PrenormalizedTemplateMetadata):
Promise<CompileTemplateMetadata> { Promise<CompileTemplateMetadata> {
let templateUrl = this._urlResolver.resolve(directiveType.moduleUrl, template.templateUrl); let templateUrl = this._urlResolver.resolve(prenomData.moduleUrl, prenomData.templateUrl);
return this._fetch(templateUrl) return this._fetch(templateUrl)
.then((value) => this.normalizeLoadedTemplate(directiveType, template, value, templateUrl)); .then((value) => this.normalizeLoadedTemplate(prenomData, value, templateUrl));
} }
normalizeLoadedTemplate( normalizeLoadedTemplate(
directiveType: CompileTypeMetadata, templateMeta: CompileTemplateMetadata, template: string, prenomData: PrenormalizedTemplateMetadata, template: string,
templateAbsUrl: string): CompileTemplateMetadata { templateAbsUrl: string): CompileTemplateMetadata {
const interpolationConfig = InterpolationConfig.fromArray(templateMeta.interpolation); const interpolationConfig = InterpolationConfig.fromArray(prenomData.interpolation);
const rootNodesAndErrors = const rootNodesAndErrors = this._htmlParser.parse(
this._htmlParser.parse(template, directiveType.name, false, interpolationConfig); template, stringify(prenomData.componentType), false, interpolationConfig);
if (rootNodesAndErrors.errors.length > 0) { if (rootNodesAndErrors.errors.length > 0) {
const errorString = rootNodesAndErrors.errors.join('\n'); const errorString = rootNodesAndErrors.errors.join('\n');
throw new Error(`Template parse errors:\n${errorString}`); throw new Error(`Template parse errors:\n${errorString}`);
} }
const templateMetadataStyles = this.normalizeStylesheet(new CompileStylesheetMetadata({ const templateMetadataStyles = this.normalizeStylesheet(new CompileStylesheetMetadata({
styles: templateMeta.styles, styles: prenomData.styles,
styleUrls: templateMeta.styleUrls, styleUrls: prenomData.styleUrls,
moduleUrl: directiveType.moduleUrl moduleUrl: prenomData.moduleUrl
})); }));
const visitor = new TemplatePreparseVisitor(); const visitor = new TemplatePreparseVisitor();
@ -114,7 +117,7 @@ export class DirectiveNormalizer {
const templateStyles = this.normalizeStylesheet(new CompileStylesheetMetadata( const templateStyles = this.normalizeStylesheet(new CompileStylesheetMetadata(
{styles: visitor.styles, styleUrls: visitor.styleUrls, moduleUrl: templateAbsUrl})); {styles: visitor.styles, styleUrls: visitor.styleUrls, moduleUrl: templateAbsUrl}));
let encapsulation = templateMeta.encapsulation; let encapsulation = prenomData.encapsulation;
if (isBlank(encapsulation)) { if (isBlank(encapsulation)) {
encapsulation = this._config.defaultEncapsulation; encapsulation = this._config.defaultEncapsulation;
} }
@ -131,10 +134,9 @@ export class DirectiveNormalizer {
encapsulation, encapsulation,
template, template,
templateUrl: templateAbsUrl, styles, styleUrls, templateUrl: templateAbsUrl, styles, styleUrls,
externalStylesheets: templateMeta.externalStylesheets,
ngContentSelectors: visitor.ngContentSelectors, ngContentSelectors: visitor.ngContentSelectors,
animations: templateMeta.animations, animations: prenomData.animations,
interpolation: templateMeta.interpolation, interpolation: prenomData.interpolation,
}); });
} }
@ -231,25 +233,3 @@ class TemplatePreparseVisitor implements html.Visitor {
visitExpansion(ast: html.Expansion, context: any): any { return null; } visitExpansion(ast: html.Expansion, context: any): any { return null; }
visitExpansionCase(ast: html.ExpansionCase, 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 { export class DirectiveResolver {
constructor(private _reflector: ReflectorReader = reflector) {} 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`. * Return {@link Directive} for a given `Type`.
*/ */

View File

@ -10,17 +10,27 @@ import {AnimationAnimateMetadata, AnimationEntryMetadata, AnimationGroupMetadata
import {assertArrayOfStrings, assertInterpolationSymbols} from './assertions'; import {assertArrayOfStrings, assertInterpolationSymbols} from './assertions';
import * as cpl from './compile_metadata'; import * as cpl from './compile_metadata';
import {DirectiveNormalizer} from './directive_normalizer';
import {DirectiveResolver} from './directive_resolver'; import {DirectiveResolver} from './directive_resolver';
import {ListWrapper} from './facade/collection';
import {isBlank, isPresent, stringify} from './facade/lang'; import {isBlank, isPresent, stringify} from './facade/lang';
import {Identifiers, resolveIdentifierToken} from './identifiers'; import {Identifiers, resolveIdentifierToken} from './identifiers';
import {hasLifecycleHook} from './lifecycle_reflector'; import {hasLifecycleHook} from './lifecycle_reflector';
import {NgModuleResolver} from './ng_module_resolver'; import {NgModuleResolver} from './ng_module_resolver';
import {PipeResolver} from './pipe_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 {ElementSchemaRegistry} from './schema/element_schema_registry';
import {getUrlScheme} from './url_resolver'; 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() @Injectable()
export class CompileMetadataResolver { export class CompileMetadataResolver {
private _directiveCache = new Map<Type<any>, cpl.CompileDirectiveMetadata>(); private _directiveCache = new Map<Type<any>, cpl.CompileDirectiveMetadata>();
@ -33,6 +43,7 @@ export class CompileMetadataResolver {
constructor( constructor(
private _ngModuleResolver: NgModuleResolver, private _directiveResolver: DirectiveResolver, private _ngModuleResolver: NgModuleResolver, private _directiveResolver: DirectiveResolver,
private _pipeResolver: PipeResolver, private _schemaRegistry: ElementSchemaRegistry, private _pipeResolver: PipeResolver, private _schemaRegistry: ElementSchemaRegistry,
private _directiveNormalizer: DirectiveNormalizer,
private _reflector: ReflectorReader = reflector) {} private _reflector: ReflectorReader = reflector) {}
private sanitizeTokenName(token: any): string { private sanitizeTokenName(token: any): string {
@ -50,11 +61,15 @@ export class CompileMetadataResolver {
} }
clearCacheFor(type: Type<any>) { clearCacheFor(type: Type<any>) {
const dirMeta = this._directiveCache.get(type);
this._directiveCache.delete(type); this._directiveCache.delete(type);
this._pipeCache.delete(type); this._pipeCache.delete(type);
this._ngModuleOfTypes.delete(type); this._ngModuleOfTypes.delete(type);
// Clear all of the NgModule as they contain transitive information! // Clear all of the NgModule as they contain transitive information!
this._ngModuleCache.clear(); this._ngModuleCache.clear();
if (dirMeta) {
this._directiveNormalizer.clearCacheFor(dirMeta);
}
} }
clearCache() { clearCache() {
@ -62,50 +77,53 @@ export class CompileMetadataResolver {
this._pipeCache.clear(); this._pipeCache.clear();
this._ngModuleCache.clear(); this._ngModuleCache.clear();
this._ngModuleOfTypes.clear(); this._ngModuleOfTypes.clear();
this._directiveNormalizer.clearCache();
} }
getAnimationEntryMetadata(entry: AnimationEntryMetadata): cpl.CompileAnimationEntryMetadata { 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); return new cpl.CompileAnimationEntryMetadata(entry.name, defs);
} }
getAnimationStateMetadata(value: AnimationStateMetadata): cpl.CompileAnimationStateMetadata { private _getAnimationStateMetadata(value: AnimationStateMetadata):
cpl.CompileAnimationStateMetadata {
if (value instanceof AnimationStateDeclarationMetadata) { if (value instanceof AnimationStateDeclarationMetadata) {
const styles = this.getAnimationStyleMetadata(value.styles); const styles = this._getAnimationStyleMetadata(value.styles);
return new cpl.CompileAnimationStateDeclarationMetadata(value.stateNameExpr, styles); return new cpl.CompileAnimationStateDeclarationMetadata(value.stateNameExpr, styles);
} }
if (value instanceof AnimationStateTransitionMetadata) { if (value instanceof AnimationStateTransitionMetadata) {
return new cpl.CompileAnimationStateTransitionMetadata( return new cpl.CompileAnimationStateTransitionMetadata(
value.stateChangeExpr, this.getAnimationMetadata(value.steps)); value.stateChangeExpr, this._getAnimationMetadata(value.steps));
} }
return null; return null;
} }
getAnimationStyleMetadata(value: AnimationStyleMetadata): cpl.CompileAnimationStyleMetadata { private _getAnimationStyleMetadata(value: AnimationStyleMetadata):
cpl.CompileAnimationStyleMetadata {
return new cpl.CompileAnimationStyleMetadata(value.offset, value.styles); return new cpl.CompileAnimationStyleMetadata(value.offset, value.styles);
} }
getAnimationMetadata(value: AnimationMetadata): cpl.CompileAnimationMetadata { private _getAnimationMetadata(value: AnimationMetadata): cpl.CompileAnimationMetadata {
if (value instanceof AnimationStyleMetadata) { if (value instanceof AnimationStyleMetadata) {
return this.getAnimationStyleMetadata(value); return this._getAnimationStyleMetadata(value);
} }
if (value instanceof AnimationKeyframesSequenceMetadata) { if (value instanceof AnimationKeyframesSequenceMetadata) {
return new cpl.CompileAnimationKeyframesSequenceMetadata( return new cpl.CompileAnimationKeyframesSequenceMetadata(
value.steps.map(entry => this.getAnimationStyleMetadata(entry))); value.steps.map(entry => this._getAnimationStyleMetadata(entry)));
} }
if (value instanceof AnimationAnimateMetadata) { if (value instanceof AnimationAnimateMetadata) {
const animateData = const animateData =
<cpl.CompileAnimationStyleMetadata|cpl.CompileAnimationKeyframesSequenceMetadata>this <cpl.CompileAnimationStyleMetadata|cpl.CompileAnimationKeyframesSequenceMetadata>this
.getAnimationMetadata(value.styles); ._getAnimationMetadata(value.styles);
return new cpl.CompileAnimationAnimateMetadata(value.timings, animateData); return new cpl.CompileAnimationAnimateMetadata(value.timings, animateData);
} }
if (value instanceof AnimationWithStepsMetadata) { 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) { if (value instanceof AnimationGroupMetadata) {
return new cpl.CompileAnimationGroupMetadata(steps); return new cpl.CompileAnimationGroupMetadata(steps);
@ -116,52 +134,35 @@ export class CompileMetadataResolver {
return null; 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); directiveType = resolveForwardRef(directiveType);
let meta = this._directiveCache.get(directiveType); const dirMeta = this._directiveResolver.resolve(directiveType);
if (!meta) {
const dirMeta = this._directiveResolver.resolve(directiveType, throwIfNotFound);
if (!dirMeta) { if (!dirMeta) {
return null; return null;
} }
let templateMeta: cpl.CompileTemplateMetadata = null; let moduleUrl = staticTypeModuleUrl(directiveType);
const createDirectiveMetadata = (templateMeta: cpl.CompileTemplateMetadata) => {
let changeDetectionStrategy: ChangeDetectionStrategy = null; let changeDetectionStrategy: ChangeDetectionStrategy = null;
let viewProviders: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = []; let viewProviders: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = [];
let moduleUrl = staticTypeModuleUrl(directiveType); let entryComponentMetadata: cpl.CompileIdentifierMetadata[] = [];
let entryComponentMetadata: cpl.CompileTypeMetadata[] = [];
let selector = dirMeta.selector; let selector = dirMeta.selector;
if (dirMeta instanceof Component) { if (dirMeta instanceof Component) {
// 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; changeDetectionStrategy = dirMeta.changeDetection;
if (dirMeta.viewProviders) { if (dirMeta.viewProviders) {
viewProviders = this.getProvidersMetadata( viewProviders = this._getProvidersMetadata(
dirMeta.viewProviders, entryComponentMetadata, dirMeta.viewProviders, entryComponentMetadata,
`viewProviders for "${stringify(directiveType)}"`); `viewProviders for "${stringify(directiveType)}"`);
} }
moduleUrl = componentModuleUrl(this._reflector, directiveType, dirMeta);
if (dirMeta.entryComponents) { if (dirMeta.entryComponents) {
entryComponentMetadata = entryComponentMetadata =
flattenAndDedupeArray(dirMeta.entryComponents) flattenAndDedupeArray(dirMeta.entryComponents)
.map((type) => this.getTypeMetadata(type, staticTypeModuleUrl(type))) .map((type) => this._getIdentifierMetadata(type, staticTypeModuleUrl(type)))
.concat(entryComponentMetadata); .concat(entryComponentMetadata);
} }
if (!selector) { if (!selector) {
@ -176,22 +177,22 @@ export class CompileMetadataResolver {
let providers: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = []; let providers: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = [];
if (isPresent(dirMeta.providers)) { if (isPresent(dirMeta.providers)) {
providers = this.getProvidersMetadata( providers = this._getProvidersMetadata(
dirMeta.providers, entryComponentMetadata, dirMeta.providers, entryComponentMetadata,
`providers for "${stringify(directiveType)}"`); `providers for "${stringify(directiveType)}"`);
} }
let queries: cpl.CompileQueryMetadata[] = []; let queries: cpl.CompileQueryMetadata[] = [];
let viewQueries: cpl.CompileQueryMetadata[] = []; let viewQueries: cpl.CompileQueryMetadata[] = [];
if (isPresent(dirMeta.queries)) { if (isPresent(dirMeta.queries)) {
queries = this.getQueriesMetadata(dirMeta.queries, false, directiveType); queries = this._getQueriesMetadata(dirMeta.queries, false, directiveType);
viewQueries = this.getQueriesMetadata(dirMeta.queries, true, directiveType); viewQueries = this._getQueriesMetadata(dirMeta.queries, true, directiveType);
} }
meta = cpl.CompileDirectiveMetadata.create({ const meta = cpl.CompileDirectiveMetadata.create({
selector: selector, selector: selector,
exportAs: dirMeta.exportAs, exportAs: dirMeta.exportAs,
isComponent: !!templateMeta, isComponent: !!templateMeta,
type: this.getTypeMetadata(directiveType, moduleUrl), type: this._getTypeMetadata(directiveType, moduleUrl),
template: templateMeta, template: templateMeta,
changeDetection: changeDetectionStrategy, changeDetection: changeDetectionStrategy,
inputs: dirMeta.inputs, inputs: dirMeta.inputs,
@ -204,27 +205,110 @@ export class CompileMetadataResolver {
entryComponents: entryComponentMetadata entryComponents: entryComponentMetadata
}); });
this._directiveCache.set(directiveType, meta); this._directiveCache.set(directiveType, meta);
}
return 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); moduleType = resolveForwardRef(moduleType);
let compileMeta = this._ngModuleCache.get(moduleType); let compileMeta = this._ngModuleCache.get(moduleType);
if (!compileMeta) { if (compileMeta) {
return compileMeta;
}
const meta = this._ngModuleResolver.resolve(moduleType, throwIfNotFound); const meta = this._ngModuleResolver.resolve(moduleType, throwIfNotFound);
if (!meta) { if (!meta) {
return null; return null;
} }
const declaredDirectives: cpl.CompileDirectiveMetadata[] = []; const declaredDirectives: cpl.CompileIdentifierMetadata[] = [];
const exportedDirectives: cpl.CompileDirectiveMetadata[] = []; const exportedDirectives: cpl.CompileIdentifierMetadata[] = [];
const declaredPipes: cpl.CompilePipeMetadata[] = []; const declaredPipes: cpl.CompileIdentifierMetadata[] = [];
const exportedPipes: cpl.CompilePipeMetadata[] = []; const exportedPipes: cpl.CompileIdentifierMetadata[] = [];
const importedModules: cpl.CompileNgModuleMetadata[] = []; const importedModules: cpl.CompileNgModuleMetadata[] = [];
const exportedModules: cpl.CompileNgModuleMetadata[] = []; const exportedModules: cpl.CompileNgModuleMetadata[] = [];
const providers: any[] = []; const providers: any[] = [];
const entryComponents: cpl.CompileTypeMetadata[] = []; const entryComponents: cpl.CompileIdentifierMetadata[] = [];
const bootstrapComponents: cpl.CompileTypeMetadata[] = []; const bootstrapComponents: cpl.CompileIdentifierMetadata[] = [];
const schemas: SchemaMetadata[] = []; const schemas: SchemaMetadata[] = [];
if (meta.imports) { if (meta.imports) {
@ -236,14 +320,14 @@ export class CompileMetadataResolver {
const moduleWithProviders: ModuleWithProviders = importedType; const moduleWithProviders: ModuleWithProviders = importedType;
importedModuleType = moduleWithProviders.ngModule; importedModuleType = moduleWithProviders.ngModule;
if (moduleWithProviders.providers) { if (moduleWithProviders.providers) {
providers.push(...this.getProvidersMetadata( providers.push(...this._getProvidersMetadata(
moduleWithProviders.providers, entryComponents, moduleWithProviders.providers, entryComponents,
`provider for the NgModule '${stringify(importedModuleType)}'`)); `provider for the NgModule '${stringify(importedModuleType)}'`));
} }
} }
if (importedModuleType) { if (importedModuleType) {
const importedMeta = this.getNgModuleMetadata(importedModuleType, false); const importedMeta = this._loadNgModuleMetadata(importedModuleType, isSync, false);
if (importedMeta === null) { if (importedMeta === null) {
throw new Error( throw new Error(
`Unexpected ${this._getTypeDescriptor(importedType)} '${stringify(importedType)}' imported by the module '${stringify(moduleType)}'`); `Unexpected ${this._getTypeDescriptor(importedType)} '${stringify(importedType)}' imported by the module '${stringify(moduleType)}'`);
@ -262,14 +346,14 @@ export class CompileMetadataResolver {
throw new Error( throw new Error(
`Unexpected value '${stringify(exportedType)}' exported by the module '${stringify(moduleType)}'`); `Unexpected value '${stringify(exportedType)}' exported by the module '${stringify(moduleType)}'`);
} }
let exportedDirMeta: cpl.CompileDirectiveMetadata; let identifier =
let exportedPipeMeta: cpl.CompilePipeMetadata; this._getIdentifierMetadata(exportedType, staticTypeModuleUrl(exportedType));
let exportedModuleMeta: cpl.CompileNgModuleMetadata; let exportedModuleMeta: cpl.CompileNgModuleMetadata;
if (exportedDirMeta = this.getDirectiveMetadata(exportedType, false)) { if (this._directiveResolver.isDirective(exportedType)) {
exportedDirectives.push(exportedDirMeta); exportedDirectives.push(identifier);
} else if (exportedPipeMeta = this.getPipeMetadata(exportedType, false)) { } else if (this._pipeResolver.isPipe(exportedType)) {
exportedPipes.push(exportedPipeMeta); exportedPipes.push(identifier);
} else if (exportedModuleMeta = this.getNgModuleMetadata(exportedType, false)) { } else if (exportedModuleMeta = this._loadNgModuleMetadata(exportedType, isSync, false)) {
exportedModules.push(exportedModuleMeta); exportedModules.push(exportedModuleMeta);
} else { } else {
throw new Error( throw new Error(
@ -280,22 +364,30 @@ export class CompileMetadataResolver {
// Note: This will be modified later, so we rely on // Note: This will be modified later, so we rely on
// getting a new instance every time! // getting a new instance every time!
const transitiveModule = const transitiveModule = this._getTransitiveNgModuleMetadata(importedModules, exportedModules);
this._getTransitiveNgModuleMetadata(importedModules, exportedModules);
if (meta.declarations) { if (meta.declarations) {
flattenAndDedupeArray(meta.declarations).forEach((declaredType) => { flattenAndDedupeArray(meta.declarations).forEach((declaredType) => {
if (!isValidType(declaredType)) { if (!isValidType(declaredType)) {
throw new Error( throw new Error(
`Unexpected value '${stringify(declaredType)}' declared by the module '${stringify(moduleType)}'`); `Unexpected value '${stringify(declaredType)}' declared by the module '${stringify(moduleType)}'`);
} }
let declaredDirMeta: cpl.CompileDirectiveMetadata; const declaredIdentifier =
let declaredPipeMeta: cpl.CompilePipeMetadata; this._getIdentifierMetadata(declaredType, staticTypeModuleUrl(declaredType));
if (declaredDirMeta = this.getDirectiveMetadata(declaredType, false)) { if (this._directiveResolver.isDirective(declaredType)) {
this._addDirectiveToModule( transitiveModule.directivesSet.add(declaredType);
declaredDirMeta, moduleType, transitiveModule, declaredDirectives, true); transitiveModule.directives.push(declaredIdentifier);
} else if (declaredPipeMeta = this.getPipeMetadata(declaredType, false)) { declaredDirectives.push(declaredIdentifier);
this._addPipeToModule( this._addTypeToModule(declaredType, moduleType);
declaredPipeMeta, moduleType, transitiveModule, declaredPipes, true); 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 { } else {
throw new Error( throw new Error(
`Unexpected ${this._getTypeDescriptor(declaredType)} '${stringify(declaredType)}' declared by the module '${stringify(moduleType)}'`); `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 // The providers of the module have to go last
// so that they overwrite any other provider we already added. // so that they overwrite any other provider we already added.
if (meta.providers) { if (meta.providers) {
providers.push(...this.getProvidersMetadata( providers.push(...this._getProvidersMetadata(
meta.providers, entryComponents, meta.providers, entryComponents, `provider for the NgModule '${stringify(moduleType)}'`));
`provider for the NgModule '${stringify(moduleType)}'`));
} }
if (meta.entryComponents) { if (meta.entryComponents) {
entryComponents.push( entryComponents.push(
...flattenAndDedupeArray(meta.entryComponents) ...flattenAndDedupeArray(meta.entryComponents)
.map(type => this.getTypeMetadata(type, staticTypeModuleUrl(type)))); .map(type => this._getTypeMetadata(type, staticTypeModuleUrl(type))));
} }
if (meta.bootstrap) { if (meta.bootstrap) {
@ -323,7 +414,7 @@ export class CompileMetadataResolver {
throw new Error( throw new Error(
`Unexpected value '${stringify(type)}' used in the bootstrap property of module '${stringify(moduleType)}'`); `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); bootstrapComponents.push(...typeMetadata);
} }
@ -338,7 +429,7 @@ export class CompileMetadataResolver {
transitiveModule.providers.push(...providers); transitiveModule.providers.push(...providers);
compileMeta = new cpl.CompileNgModuleMetadata({ compileMeta = new cpl.CompileNgModuleMetadata({
type: this.getTypeMetadata(moduleType, staticTypeModuleUrl(moduleType)), type: this._getTypeMetadata(moduleType, staticTypeModuleUrl(moduleType)),
providers, providers,
entryComponents, entryComponents,
bootstrapComponents, bootstrapComponents,
@ -356,37 +447,36 @@ export class CompileMetadataResolver {
transitiveModule.modules.push(compileMeta); transitiveModule.modules.push(compileMeta);
this._verifyModule(compileMeta); this._verifyModule(compileMeta);
this._ngModuleCache.set(moduleType, compileMeta); this._ngModuleCache.set(moduleType, compileMeta);
}
return compileMeta; return compileMeta;
} }
private _verifyModule(moduleMeta: cpl.CompileNgModuleMetadata) { private _verifyModule(moduleMeta: cpl.CompileNgModuleMetadata) {
moduleMeta.exportedDirectives.forEach((dirMeta) => { moduleMeta.exportedDirectives.forEach((dirIdentifier) => {
if (!moduleMeta.transitiveModule.directivesSet.has(dirMeta.type.reference)) { if (!moduleMeta.transitiveModule.directivesSet.has(dirIdentifier.reference)) {
throw new Error( 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) => { moduleMeta.exportedPipes.forEach((pipeIdentifier) => {
if (!moduleMeta.transitiveModule.pipesSet.has(pipeMeta.type.reference)) { if (!moduleMeta.transitiveModule.pipesSet.has(pipeIdentifier.reference)) {
throw new Error( 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 { private _getTypeDescriptor(type: Type<any>): string {
if (this._directiveResolver.resolve(type, false)) { if (this._directiveResolver.isDirective(type)) {
return 'directive'; return 'directive';
} }
if (this._pipeResolver.resolve(type, false)) { if (this._pipeResolver.isPipe(type)) {
return 'pipe'; return 'pipe';
} }
if (this._ngModuleResolver.resolve(type, false)) { if (this._ngModuleResolver.isNgModule(type)) {
return 'module'; return 'module';
} }
@ -397,6 +487,7 @@ export class CompileMetadataResolver {
return 'value'; return 'value';
} }
private _addTypeToModule(type: Type<any>, moduleType: Type<any>) { private _addTypeToModule(type: Type<any>, moduleType: Type<any>) {
const oldModule = this._ngModuleOfTypes.get(type); const oldModule = this._ngModuleOfTypes.get(type);
if (oldModule && oldModule !== moduleType) { if (oldModule && oldModule !== moduleType) {
@ -421,81 +512,72 @@ export class CompileMetadataResolver {
const directives = const directives =
flattenArray(transitiveExportedModules.map((ngModule) => ngModule.exportedDirectives)); flattenArray(transitiveExportedModules.map((ngModule) => ngModule.exportedDirectives));
const pipes = flattenArray(transitiveExportedModules.map((ngModule) => ngModule.exportedPipes)); const pipes = flattenArray(transitiveExportedModules.map((ngModule) => ngModule.exportedPipes));
const loadingPromises = ListWrapper.flatten(
transitiveExportedModules.map(ngModule => ngModule.transitiveModule.loadingPromises));
return new cpl.TransitiveCompileNgModuleMetadata( return new cpl.TransitiveCompileNgModuleMetadata(
transitiveModules, providers, entryComponents, directives, pipes); transitiveModules, providers, entryComponents, directives, pipes, loadingPromises);
} }
private _addDirectiveToModule( private _getIdentifierMetadata(type: Type<any>, moduleUrl: string):
dirMeta: cpl.CompileDirectiveMetadata, moduleType: any, cpl.CompileIdentifierMetadata {
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 {
type = resolveForwardRef(type); 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({ return new cpl.CompileTypeMetadata({
name: this.sanitizeTokenName(type), name: identifier.name,
moduleUrl, moduleUrl: identifier.moduleUrl,
reference: type, reference: identifier.reference,
diDeps: this.getDependenciesMetadata(type, dependencies), diDeps: this._getDependenciesMetadata(identifier.reference, dependencies),
lifecycleHooks: LIFECYCLE_HOOKS_VALUES.filter(hook => hasLifecycleHook(hook, type)), 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 { cpl.CompileFactoryMetadata {
factory = resolveForwardRef(factory); factory = resolveForwardRef(factory);
return new cpl.CompileFactoryMetadata({ return new cpl.CompileFactoryMetadata({
name: this.sanitizeTokenName(factory), name: this.sanitizeTokenName(factory),
moduleUrl, moduleUrl,
reference: factory, 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); pipeType = resolveForwardRef(pipeType);
let meta = this._pipeCache.get(pipeType); const pipeMeta = this._pipeResolver.resolve(pipeType);
if (!meta) {
const pipeMeta = this._pipeResolver.resolve(pipeType, throwIfNotFound);
if (!pipeMeta) { if (!pipeMeta) {
return null; return null;
} }
meta = new cpl.CompilePipeMetadata({ const meta = new cpl.CompilePipeMetadata({
type: this.getTypeMetadata(pipeType, staticTypeModuleUrl(pipeType)), type: this._getTypeMetadata(pipeType, staticTypeModuleUrl(pipeType)),
name: pipeMeta.name, name: pipeMeta.name,
pure: pipeMeta.pure pure: pipeMeta.pure
}); });
this._pipeCache.set(pipeType, meta); this._pipeCache.set(pipeType, meta);
} }
return meta;
}
getDependenciesMetadata(typeOrFunc: Type<any>|Function, dependencies: any[]): private _getDependenciesMetadata(typeOrFunc: Type<any>|Function, dependencies: any[]):
cpl.CompileDiDependencyMetadata[] { cpl.CompileDiDependencyMetadata[] {
let hasUnknownDeps = false; let hasUnknownDeps = false;
let params = dependencies || this._reflector.parameters(typeOrFunc) || []; let params = dependencies || this._reflector.parameters(typeOrFunc) || [];
@ -540,7 +622,7 @@ export class CompileMetadataResolver {
isSelf, isSelf,
isSkipSelf, isSkipSelf,
isOptional, isOptional,
token: this.getTokenMetadata(token) token: this._getTokenMetadata(token)
}); });
}); });
@ -555,7 +637,7 @@ export class CompileMetadataResolver {
return dependenciesMetadata; return dependenciesMetadata;
} }
getTokenMetadata(token: any): cpl.CompileTokenMetadata { private _getTokenMetadata(token: any): cpl.CompileTokenMetadata {
token = resolveForwardRef(token); token = resolveForwardRef(token);
let compileToken: cpl.CompileTokenMetadata; let compileToken: cpl.CompileTokenMetadata;
if (typeof token === 'string') { if (typeof token === 'string') {
@ -572,8 +654,8 @@ export class CompileMetadataResolver {
return compileToken; return compileToken;
} }
getProvidersMetadata( private _getProvidersMetadata(
providers: Provider[], targetEntryComponents: cpl.CompileTypeMetadata[], providers: Provider[], targetEntryComponents: cpl.CompileIdentifierMetadata[],
debugInfo?: string): Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> { debugInfo?: string): Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> {
const compileProviders: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = []; const compileProviders: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = [];
providers.forEach((provider: any, providerIdx: number) => { providers.forEach((provider: any, providerIdx: number) => {
@ -583,9 +665,9 @@ export class CompileMetadataResolver {
} }
let compileProvider: cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]; let compileProvider: cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[];
if (Array.isArray(provider)) { if (Array.isArray(provider)) {
compileProvider = this.getProvidersMetadata(provider, targetEntryComponents, debugInfo); compileProvider = this._getProvidersMetadata(provider, targetEntryComponents, debugInfo);
} else if (provider instanceof cpl.ProviderMeta) { } else if (provider instanceof cpl.ProviderMeta) {
let tokenMeta = this.getTokenMetadata(provider.token); let tokenMeta = this._getTokenMetadata(provider.token);
if (tokenMeta.reference === if (tokenMeta.reference ===
resolveIdentifierToken(Identifiers.ANALYZE_FOR_ENTRY_COMPONENTS).reference) { resolveIdentifierToken(Identifiers.ANALYZE_FOR_ENTRY_COMPONENTS).reference) {
targetEntryComponents.push(...this._getEntryComponentsFromProvider(provider)); targetEntryComponents.push(...this._getEntryComponentsFromProvider(provider));
@ -593,7 +675,7 @@ export class CompileMetadataResolver {
compileProvider = this.getProviderMetadata(provider); compileProvider = this.getProviderMetadata(provider);
} }
} else if (isValidType(provider)) { } else if (isValidType(provider)) {
compileProvider = this.getTypeMetadata(provider, staticTypeModuleUrl(provider)); compileProvider = this._getTypeMetadata(provider, staticTypeModuleUrl(provider));
} else { } else {
const providersInfo = const providersInfo =
(<string[]>providers.reduce( (<string[]>providers.reduce(
@ -620,8 +702,9 @@ export class CompileMetadataResolver {
return compileProviders; return compileProviders;
} }
private _getEntryComponentsFromProvider(provider: cpl.ProviderMeta): cpl.CompileTypeMetadata[] { private _getEntryComponentsFromProvider(provider: cpl.ProviderMeta):
const components: cpl.CompileTypeMetadata[] = []; cpl.CompileIdentifierMetadata[] {
const components: cpl.CompileIdentifierMetadata[] = [];
const collectedIdentifiers: cpl.CompileIdentifierMetadata[] = []; const collectedIdentifiers: cpl.CompileIdentifierMetadata[] = [];
if (provider.useFactory || provider.useExisting || provider.useClass) { if (provider.useFactory || provider.useExisting || provider.useClass) {
@ -634,9 +717,8 @@ export class CompileMetadataResolver {
convertToCompileValue(provider.useValue, collectedIdentifiers); convertToCompileValue(provider.useValue, collectedIdentifiers);
collectedIdentifiers.forEach((identifier) => { collectedIdentifiers.forEach((identifier) => {
const dirMeta = this.getDirectiveMetadata(identifier.reference, false); if (this._directiveResolver.isDirective(identifier.reference)) {
if (dirMeta) { components.push(identifier);
components.push(dirMeta.type);
} }
}); });
return components; return components;
@ -648,27 +730,27 @@ export class CompileMetadataResolver {
let compileFactoryMetadata: cpl.CompileFactoryMetadata = null; let compileFactoryMetadata: cpl.CompileFactoryMetadata = null;
if (provider.useClass) { if (provider.useClass) {
compileTypeMetadata = this.getTypeMetadata( compileTypeMetadata = this._getTypeMetadata(
provider.useClass, staticTypeModuleUrl(provider.useClass), provider.dependencies); provider.useClass, staticTypeModuleUrl(provider.useClass), provider.dependencies);
compileDeps = compileTypeMetadata.diDeps; compileDeps = compileTypeMetadata.diDeps;
} else if (provider.useFactory) { } else if (provider.useFactory) {
compileFactoryMetadata = this.getFactoryMetadata( compileFactoryMetadata = this._getFactoryMetadata(
provider.useFactory, staticTypeModuleUrl(provider.useFactory), provider.dependencies); provider.useFactory, staticTypeModuleUrl(provider.useFactory), provider.dependencies);
compileDeps = compileFactoryMetadata.diDeps; compileDeps = compileFactoryMetadata.diDeps;
} }
return new cpl.CompileProviderMetadata({ return new cpl.CompileProviderMetadata({
token: this.getTokenMetadata(provider.token), token: this._getTokenMetadata(provider.token),
useClass: compileTypeMetadata, useClass: compileTypeMetadata,
useValue: convertToCompileValue(provider.useValue, []), useValue: convertToCompileValue(provider.useValue, []),
useFactory: compileFactoryMetadata, useFactory: compileFactoryMetadata,
useExisting: provider.useExisting ? this.getTokenMetadata(provider.useExisting) : null, useExisting: provider.useExisting ? this._getTokenMetadata(provider.useExisting) : null,
deps: compileDeps, deps: compileDeps,
multi: provider.multi multi: provider.multi
}); });
} }
getQueriesMetadata( private _getQueriesMetadata(
queries: {[key: string]: Query}, isViewQuery: boolean, queries: {[key: string]: Query}, isViewQuery: boolean,
directiveType: Type<any>): cpl.CompileQueryMetadata[] { directiveType: Type<any>): cpl.CompileQueryMetadata[] {
const res: cpl.CompileQueryMetadata[] = []; const res: cpl.CompileQueryMetadata[] = [];
@ -676,7 +758,7 @@ export class CompileMetadataResolver {
Object.keys(queries).forEach((propertyName: string) => { Object.keys(queries).forEach((propertyName: string) => {
const query = queries[propertyName]; const query = queries[propertyName];
if (query.isViewQuery === isViewQuery) { 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*/); } 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 { cpl.CompileQueryMetadata {
var selectors: cpl.CompileTokenMetadata[]; var selectors: cpl.CompileTokenMetadata[];
if (typeof q.selector === 'string') { 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 { } else {
if (!q.selector) { if (!q.selector) {
throw new Error( throw new Error(
`Can't construct a query for the property "${propertyName}" of "${stringify(typeOrFunc)}" since the query selector wasn't defined.`); `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({ return new cpl.CompileQueryMetadata({
selectors, selectors,
first: q.first, first: q.first,
descendants: q.descendants, propertyName, 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 { export class NgModuleResolver {
constructor(private _reflector: ReflectorReader = reflector) {} constructor(private _reflector: ReflectorReader = reflector) {}
isNgModule(type: any) { return this._reflector.annotations(type).some(_isNgModuleMetadata); }
resolve(type: Type<any>, throwIfNotFound = true): NgModule { resolve(type: Type<any>, throwIfNotFound = true): NgModule {
const ngModuleMeta: NgModule = this._reflector.annotations(type).find(_isNgModuleMetadata); 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 // Returns all the source files and a mapping from modules to directives
export function analyzeNgModules( export function analyzeNgModules(
programStaticSymbols: StaticSymbol[], options: {transitiveModules: boolean}, programStaticSymbols: StaticSymbol[], options: {transitiveModules: boolean},
metadataResolver: CompileMetadataResolver): { metadataResolver: CompileMetadataResolver): Promise<{
ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>, ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
files: Array<{srcUrl: string, directives: StaticSymbol[], ngModules: StaticSymbol[]}> files: Array<{srcUrl: string, directives: StaticSymbol[], ngModules: StaticSymbol[]}>
} { }> {
const { return _loadNgModules(programStaticSymbols, options, metadataResolver).then(_analyzeNgModules);
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);
} }
});
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 ngModuleByPipeOrDirective = new Map<StaticSymbol, CompileNgModuleMetadata>();
const ngModulesByFile = new Map<string, StaticSymbol[]>(); const ngModulesByFile = new Map<string, StaticSymbol[]>();
const ngDirectivesByFile = new Map<string, StaticSymbol[]>(); const ngDirectivesByFile = new Map<string, StaticSymbol[]>();
@ -67,31 +55,20 @@ export function analyzeNgModules(
ngModulesByFile.set( ngModulesByFile.set(
srcFileUrl, (ngModulesByFile.get(srcFileUrl) || []).concat(ngModuleMeta.type.reference)); srcFileUrl, (ngModulesByFile.get(srcFileUrl) || []).concat(ngModuleMeta.type.reference));
ngModuleMeta.declaredDirectives.forEach((dirMeta: CompileDirectiveMetadata) => { ngModuleMeta.declaredDirectives.forEach((dirIdentifier) => {
const fileUrl = dirMeta.type.reference.filePath; const fileUrl = dirIdentifier.reference.filePath;
filePaths.add(fileUrl); filePaths.add(fileUrl);
ngDirectivesByFile.set( ngDirectivesByFile.set(
fileUrl, (ngDirectivesByFile.get(fileUrl) || []).concat(dirMeta.type.reference)); fileUrl, (ngDirectivesByFile.get(fileUrl) || []).concat(dirIdentifier.reference));
ngModuleByPipeOrDirective.set(dirMeta.type.reference, ngModuleMeta); ngModuleByPipeOrDirective.set(dirIdentifier.reference, ngModuleMeta);
}); });
ngModuleMeta.declaredPipes.forEach((pipeIdentifier) => {
ngModuleMeta.declaredPipes.forEach((pipeMeta: CompilePipeMetadata) => { const fileUrl = pipeIdentifier.reference.filePath;
const fileUrl = pipeMeta.type.reference.filePath;
filePaths.add(fileUrl); 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[]}[] = []; const files: {srcUrl: string, directives: StaticSymbol[], ngModules: StaticSymbol[]}[] = [];
filePaths.forEach((srcUrl) => { filePaths.forEach((srcUrl) => {
@ -112,35 +89,29 @@ export class OfflineCompiler {
private _animationCompiler = new AnimationCompiler(); private _animationCompiler = new AnimationCompiler();
constructor( constructor(
private _metadataResolver: CompileMetadataResolver, private _metadataResolver: CompileMetadataResolver, private _templateParser: TemplateParser,
private _directiveNormalizer: DirectiveNormalizer, private _templateParser: TemplateParser,
private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler, private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler,
private _dirWrapperCompiler: DirectiveWrapperCompiler, private _dirWrapperCompiler: DirectiveWrapperCompiler,
private _ngModuleCompiler: NgModuleCompiler, private _outputEmitter: OutputEmitter, private _ngModuleCompiler: NgModuleCompiler, private _outputEmitter: OutputEmitter,
private _localeId: string, private _translationFormat: string, private _localeId: string, private _translationFormat: string,
private _animationParser: AnimationParser) {} private _animationParser: AnimationParser) {}
clearCache() { clearCache() { this._metadataResolver.clearCache(); }
this._directiveNormalizer.clearCache();
this._metadataResolver.clearCache();
}
compileModules(staticSymbols: StaticSymbol[], options: {transitiveModules: boolean}): compileModules(staticSymbols: StaticSymbol[], options: {transitiveModules: boolean}):
Promise<SourceModule[]> { Promise<SourceModule[]> {
const {ngModuleByPipeOrDirective, files} = return analyzeNgModules(staticSymbols, options, this._metadataResolver)
analyzeNgModules(staticSymbols, options, this._metadataResolver); .then(({ngModuleByPipeOrDirective, files}) => {
const sourceModules = files.map( const sourceModules = files.map(
file => this._compileSrcFile( file => this._compileSrcFile(
file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.ngModules)); file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.ngModules));
return ListWrapper.flatten(sourceModules);
return Promise.all(sourceModules) });
.then((modules: SourceModule[][]) => ListWrapper.flatten(modules));
} }
private _compileSrcFile( private _compileSrcFile(
srcFileUrl: string, ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>, srcFileUrl: string, ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
directives: StaticSymbol[], ngModules: StaticSymbol[]): Promise<SourceModule[]> { directives: StaticSymbol[], ngModules: StaticSymbol[]): SourceModule[] {
const fileSuffix = _splitTypescriptSuffix(srcFileUrl)[1]; const fileSuffix = _splitTypescriptSuffix(srcFileUrl)[1];
const statements: o.Statement[] = []; const statements: o.Statement[] = [];
const exportedVars: string[] = []; const exportedVars: string[] = [];
@ -155,8 +126,7 @@ export class OfflineCompiler {
(directiveType) => this._compileDirectiveWrapper(directiveType, statements))); (directiveType) => this._compileDirectiveWrapper(directiveType, statements)));
// compile components // compile components
return Promise directives.forEach((dirType) => {
.all(directives.map((dirType) => {
const compMeta = this._metadataResolver.getDirectiveMetadata(<any>dirType); const compMeta = this._metadataResolver.getDirectiveMetadata(<any>dirType);
if (!compMeta.isComponent) { if (!compMeta.isComponent) {
return Promise.resolve(null); return Promise.resolve(null);
@ -167,36 +137,27 @@ export class OfflineCompiler {
`Internal Error: cannot determine the module for component ${compMeta.type.name}!`); `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); _assertComponent(compMeta);
// compile styles // compile styles
const stylesCompileResults = this._styleCompiler.compileComponent(compMeta); const stylesCompileResults = this._styleCompiler.compileComponent(compMeta);
stylesCompileResults.externalStylesheets.forEach((compiledStyleSheet) => { stylesCompileResults.externalStylesheets.forEach((compiledStyleSheet) => {
outputSourceModules.push( outputSourceModules.push(this._codgenStyles(srcFileUrl, compiledStyleSheet, fileSuffix));
this._codgenStyles(srcFileUrl, compiledStyleSheet, fileSuffix));
}); });
// compile components // compile components
exportedVars.push( exportedVars.push(
this._compileComponentFactory(compMeta, fileSuffix, statements), this._compileComponentFactory(compMeta, ngModule, fileSuffix, statements),
this._compileComponent( this._compileComponent(
compMeta, dirMetas, ngModule.transitiveModule.pipes, ngModule.schemas, compMeta, ngModule, ngModule.transitiveModule.directives,
stylesCompileResults.componentStylesheet, fileSuffix, statements)); stylesCompileResults.componentStylesheet, fileSuffix, statements));
}); });
}))
.then(() => {
if (statements.length > 0) { if (statements.length > 0) {
const srcModule = this._codegenSourceModule( const srcModule = this._codegenSourceModule(
srcFileUrl, _ngfactoryModuleUrl(srcFileUrl), statements, exportedVars); srcFileUrl, _ngfactoryModuleUrl(srcFileUrl), statements, exportedVars);
outputSourceModules.unshift(srcModule); outputSourceModules.unshift(srcModule);
} }
return outputSourceModules; return outputSourceModules;
});
} }
private _compileModule(ngModuleType: StaticSymbol, targetStatements: o.Statement[]): string { private _compileModule(ngModuleType: StaticSymbol, targetStatements: o.Statement[]): string {
@ -238,11 +199,11 @@ export class OfflineCompiler {
} }
private _compileComponentFactory( private _compileComponentFactory(
compMeta: CompileDirectiveMetadata, fileSuffix: string, compMeta: CompileDirectiveMetadata, ngModule: CompileNgModuleMetadata, fileSuffix: string,
targetStatements: o.Statement[]): string { targetStatements: o.Statement[]): string {
const hostMeta = createHostComponentMeta(compMeta); const hostMeta = createHostComponentMeta(compMeta);
const hostViewFactoryVar = const hostViewFactoryVar = this._compileComponent(
this._compileComponent(hostMeta, [compMeta], [], [], null, fileSuffix, targetStatements); hostMeta, ngModule, [compMeta.type], null, fileSuffix, targetStatements);
const compFactoryVar = _componentFactoryName(compMeta.type); const compFactoryVar = _componentFactoryName(compMeta.type);
targetStatements.push( targetStatements.push(
o.variable(compFactoryVar) o.variable(compFactoryVar)
@ -262,12 +223,18 @@ export class OfflineCompiler {
} }
private _compileComponent( private _compileComponent(
compMeta: CompileDirectiveMetadata, directives: CompileDirectiveMetadata[], compMeta: CompileDirectiveMetadata, ngModule: CompileNgModuleMetadata,
pipes: CompilePipeMetadata[], schemas: SchemaMetadata[], componentStyles: CompiledStylesheet, directiveIdentifiers: CompileIdentifierMetadata[], componentStyles: CompiledStylesheet,
fileSuffix: string, targetStatements: o.Statement[]): string { fileSuffix: string, targetStatements: o.Statement[]): string {
const parsedAnimations = this._animationParser.parseComponent(compMeta); 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( 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 stylesExpr = componentStyles ? o.variable(componentStyles.stylesVar) : o.literalArr([]);
const compiledAnimations = const compiledAnimations =
this._animationCompiler.compile(compMeta.type.name, parsedAnimations); this._animationCompiler.compile(compMeta.type.name, parsedAnimations);
@ -358,27 +325,50 @@ function _splitTypescriptSuffix(path: string): string[] {
return [path, '']; return [path, ''];
} }
// Group the symbols by types: // Load the NgModules and check
// - NgModules, // that all directives / pipes that are present in the program
// - Pipes and Directives. // are also declared by a module.
function _extractModulesAndPipesOrDirectives( function _loadNgModules(
programStaticSymbols: StaticSymbol[], metadataResolver: CompileMetadataResolver) { programStaticSymbols: StaticSymbol[], options: {transitiveModules: boolean},
const ngModules: CompileNgModuleMetadata[] = []; metadataResolver: CompileMetadataResolver): Promise<CompileNgModuleMetadata[]> {
const pipesAndDirectives: StaticSymbol[] = []; const ngModules = new Map<any, CompileNgModuleMetadata>();
const programPipesAndDirectives: StaticSymbol[] = [];
programStaticSymbols.forEach(staticSymbol => { const ngModulePipesAndDirective = new Set<StaticSymbol>();
const ngModule = metadataResolver.getNgModuleMetadata(staticSymbol, false); const loadingPromises: Promise<any>[] = [];
const directive = metadataResolver.getDirectiveMetadata(staticSymbol, false);
const pipe = metadataResolver.getPipeMetadata(<any>staticSymbol, false);
const addNgModule = (staticSymbol: any) => {
if (ngModules.has(staticSymbol)) {
return false;
}
const {ngModule, loading} = metadataResolver.loadNgModuleMetadata(staticSymbol, false, false);
if (ngModule) { if (ngModule) {
ngModules.push(ngModule); ngModules.set(ngModule.type.reference, ngModule);
} else if (directive) { loadingPromises.push(loading);
pipesAndDirectives.push(staticSymbol); ngModule.declaredDirectives.forEach((dir) => ngModulePipesAndDirective.add(dir.reference));
} else if (pipe) { ngModule.declaredPipes.forEach((pipe) => ngModulePipesAndDirective.add(pipe.reference));
pipesAndDirectives.push(staticSymbol); 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 { export class PipeResolver {
constructor(private _reflector: ReflectorReader = reflector) {} 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`. * 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 * as ir from './output/output_ast';
import {interpretStatements} from './output/output_interpreter'; import {interpretStatements} from './output/output_interpreter';
import {jitStatements} from './output/output_jit'; import {jitStatements} from './output/output_jit';
import {ComponentStillLoadingError} from './private_import_core';
import {CompiledStylesheet, StyleCompiler} from './style_compiler'; import {CompiledStylesheet, StyleCompiler} from './style_compiler';
import {TemplateParser} from './template_parser/template_parser'; import {TemplateParser} from './template_parser/template_parser';
import {SyncAsyncResult} from './util'; import {SyncAsyncResult} from './util';
@ -47,9 +46,8 @@ export class RuntimeCompiler implements Compiler {
constructor( constructor(
private _injector: Injector, private _metadataResolver: CompileMetadataResolver, private _injector: Injector, private _metadataResolver: CompileMetadataResolver,
private _templateNormalizer: DirectiveNormalizer, private _templateParser: TemplateParser, private _templateParser: TemplateParser, private _styleCompiler: StyleCompiler,
private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler, private _viewCompiler: ViewCompiler, private _ngModuleCompiler: NgModuleCompiler,
private _ngModuleCompiler: NgModuleCompiler,
private _directiveWrapperCompiler: DirectiveWrapperCompiler, private _directiveWrapperCompiler: DirectiveWrapperCompiler,
private _compilerConfig: CompilerConfig, private _animationParser: AnimationParser) {} private _compilerConfig: CompilerConfig, private _animationParser: AnimationParser) {}
@ -74,38 +72,47 @@ export class RuntimeCompiler implements Compiler {
private _compileModuleAndComponents<T>(moduleType: Type<T>, isSync: boolean): private _compileModuleAndComponents<T>(moduleType: Type<T>, isSync: boolean):
SyncAsyncResult<NgModuleFactory<T>> { SyncAsyncResult<NgModuleFactory<T>> {
const componentPromise = this._compileComponents(moduleType, isSync); const loadingPromise = this._loadModules(moduleType, isSync);
const ngModuleFactory = this._compileModule(moduleType); const createResult = () => {
return new SyncAsyncResult(ngModuleFactory, componentPromise.then(() => ngModuleFactory)); 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): private _compileModuleAndAllComponents<T>(moduleType: Type<T>, isSync: boolean):
SyncAsyncResult<ModuleWithComponentFactories<T>> { SyncAsyncResult<ModuleWithComponentFactories<T>> {
const componentPromise = this._compileComponents(moduleType, isSync); const loadingPromise = this._loadModules(moduleType, isSync);
const ngModuleFactory = this._compileModule(moduleType); const createResult = () => {
const moduleMeta = this._metadataResolver.getNgModuleMetadata(moduleType);
const componentFactories: ComponentFactory<any>[] = []; const componentFactories: ComponentFactory<any>[] = [];
const templates = new Set<CompiledTemplate>(); this._compileComponents(moduleType, componentFactories);
moduleMeta.transitiveModule.modules.forEach((localModuleMeta) => { return new ModuleWithComponentFactories(this._compileModule(moduleType), componentFactories);
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;
}; };
const asyncResult = isSync ? Promise.resolve(compile()) : componentPromise.then(compile); if (isSync) {
return new SyncAsyncResult(syncResult, asyncResult); 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> { private _compileModule<T>(moduleType: Type<T>): NgModuleFactory<T> {
@ -137,23 +144,30 @@ export class RuntimeCompiler implements Compiler {
/** /**
* @internal * @internal
*/ */
_compileComponents(mainModule: Type<any>, isSync: boolean): Promise<any> { _compileComponents(mainModule: Type<any>, allComponentFactories: ComponentFactory<any>[]) {
const templates = new Set<CompiledTemplate>();
var loadingPromises: Promise<any>[] = [];
const ngModule = this._metadataResolver.getNgModuleMetadata(mainModule); const ngModule = this._metadataResolver.getNgModuleMetadata(mainModule);
const moduleByDirective = new Map<any, CompileNgModuleMetadata>(); const moduleByDirective = new Map<any, CompileNgModuleMetadata>();
const templates = new Set<CompiledTemplate>();
ngModule.transitiveModule.modules.forEach((localModuleMeta) => { ngModule.transitiveModule.modules.forEach((localModuleMeta) => {
localModuleMeta.declaredDirectives.forEach((dirMeta) => { localModuleMeta.declaredDirectives.forEach((dirIdentifier) => {
moduleByDirective.set(dirMeta.type.reference, localModuleMeta); moduleByDirective.set(dirIdentifier.reference, localModuleMeta);
const dirMeta = this._metadataResolver.getDirectiveMetadata(dirIdentifier.reference);
this._compileDirectiveWrapper(dirMeta, localModuleMeta); this._compileDirectiveWrapper(dirMeta, localModuleMeta);
if (dirMeta.isComponent) { if (dirMeta.isComponent) {
templates.add(this._createCompiledTemplate(dirMeta, localModuleMeta)); 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) => { ngModule.transitiveModule.modules.forEach((localModuleMeta) => {
localModuleMeta.declaredDirectives.forEach((dirMeta) => { localModuleMeta.declaredDirectives.forEach((dirIdentifier) => {
const dirMeta = this._metadataResolver.getDirectiveMetadata(dirIdentifier.reference);
if (dirMeta.isComponent) { if (dirMeta.isComponent) {
dirMeta.entryComponents.forEach((entryComponentType) => { dirMeta.entryComponents.forEach((entryComponentType) => {
const moduleMeta = moduleByDirective.get(entryComponentType.reference); const moduleMeta = moduleByDirective.get(entryComponentType.reference);
@ -167,24 +181,7 @@ export class RuntimeCompiler implements Compiler {
templates.add(this._createCompiledHostTemplate(entryComponentType.reference, moduleMeta)); templates.add(this._createCompiledHostTemplate(entryComponentType.reference, moduleMeta));
}); });
}); });
templates.forEach((template) => this._compileTemplate(template));
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);
}
} }
clearCacheFor(type: Type<any>) { clearCacheFor(type: Type<any>) {
@ -193,7 +190,6 @@ export class RuntimeCompiler implements Compiler {
this._compiledHostTemplateCache.delete(type); this._compiledHostTemplateCache.delete(type);
var compiledTemplate = this._compiledTemplateCache.get(type); var compiledTemplate = this._compiledTemplateCache.get(type);
if (compiledTemplate) { if (compiledTemplate) {
this._templateNormalizer.clearCacheFor(compiledTemplate.normalizedCompMeta);
this._compiledTemplateCache.delete(type); this._compiledTemplateCache.delete(type);
} }
} }
@ -202,7 +198,6 @@ export class RuntimeCompiler implements Compiler {
this._metadataResolver.clearCache(); this._metadataResolver.clearCache();
this._compiledTemplateCache.clear(); this._compiledTemplateCache.clear();
this._compiledHostTemplateCache.clear(); this._compiledHostTemplateCache.clear();
this._templateNormalizer.clearCache();
this._compiledNgModuleCache.clear(); this._compiledNgModuleCache.clear();
} }
@ -218,8 +213,7 @@ export class RuntimeCompiler implements Compiler {
assertComponent(compMeta); assertComponent(compMeta);
var hostMeta = createHostComponentMeta(compMeta); var hostMeta = createHostComponentMeta(compMeta);
compiledTemplate = new CompiledTemplate( compiledTemplate = new CompiledTemplate(
true, compMeta.selector, compMeta.type, ngModule, [compMeta], true, compMeta.selector, compMeta.type, hostMeta, ngModule, [compMeta.type]);
this._templateNormalizer.normalizeDirective(hostMeta));
this._compiledHostTemplateCache.set(compType, compiledTemplate); this._compiledHostTemplateCache.set(compType, compiledTemplate);
} }
return compiledTemplate; return compiledTemplate;
@ -231,8 +225,8 @@ export class RuntimeCompiler implements Compiler {
if (!compiledTemplate) { if (!compiledTemplate) {
assertComponent(compMeta); assertComponent(compMeta);
compiledTemplate = new CompiledTemplate( compiledTemplate = new CompiledTemplate(
false, compMeta.selector, compMeta.type, ngModule, ngModule.transitiveModule.directives, false, compMeta.selector, compMeta.type, compMeta, ngModule,
this._templateNormalizer.normalizeDirective(compMeta)); ngModule.transitiveModule.directives);
this._compiledTemplateCache.set(compMeta.type.reference, compiledTemplate); this._compiledTemplateCache.set(compMeta.type.reference, compiledTemplate);
} }
return compiledTemplate; return compiledTemplate;
@ -243,16 +237,7 @@ export class RuntimeCompiler implements Compiler {
this._compiledTemplateCache.get(compType); this._compiledTemplateCache.get(compType);
if (!compiledTemplate) { if (!compiledTemplate) {
throw new Error( throw new Error(
`Illegal state: Compiled view for component ${stringify(compType)} does not exist!`); `Illegal state: Compiled view for component ${stringify(compType)} (host: ${isHost}) 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!`);
} }
return compiledTemplate; return compiledTemplate;
} }
@ -285,34 +270,36 @@ export class RuntimeCompiler implements Compiler {
if (template.isCompiled) { if (template.isCompiled) {
return; return;
} }
const compMeta = template.normalizedCompMeta; const compMeta = template.compMeta;
const externalStylesheetsByModuleUrl = new Map<string, CompiledStylesheet>(); const externalStylesheetsByModuleUrl = new Map<string, CompiledStylesheet>();
const stylesCompileResult = this._styleCompiler.compileComponent(compMeta); const stylesCompileResult = this._styleCompiler.compileComponent(compMeta);
stylesCompileResult.externalStylesheets.forEach( stylesCompileResult.externalStylesheets.forEach(
(r) => { externalStylesheetsByModuleUrl.set(r.meta.moduleUrl, r); }); (r) => { externalStylesheetsByModuleUrl.set(r.meta.moduleUrl, r); });
this._resolveStylesCompileResult( this._resolveStylesCompileResult(
stylesCompileResult.componentStylesheet, externalStylesheetsByModuleUrl); stylesCompileResult.componentStylesheet, externalStylesheetsByModuleUrl);
const viewCompMetas = template.viewComponentTypes.map(
(compType) => this._assertComponentLoaded(compType, false).normalizedCompMeta);
const parsedAnimations = this._animationParser.parseComponent(compMeta); 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( const parsedTemplate = this._templateParser.parse(
compMeta, compMeta.template.template, template.viewDirectives.concat(viewCompMetas), compMeta, compMeta.template.template, directives, pipes, template.ngModule.schemas,
template.viewPipes, template.schemas, compMeta.type.name); compMeta.type.name);
const compiledAnimations = const compiledAnimations =
this._animationCompiler.compile(compMeta.type.name, parsedAnimations); this._animationCompiler.compile(compMeta.type.name, parsedAnimations);
const compileResult = this._viewCompiler.compileComponent( const compileResult = this._viewCompiler.compileComponent(
compMeta, parsedTemplate, ir.variable(stylesCompileResult.componentStylesheet.stylesVar), compMeta, parsedTemplate, ir.variable(stylesCompileResult.componentStylesheet.stylesVar),
template.viewPipes, compiledAnimations); pipes, compiledAnimations);
compileResult.dependencies.forEach((dep) => { compileResult.dependencies.forEach((dep) => {
let depTemplate: CompiledTemplate; let depTemplate: CompiledTemplate;
if (dep instanceof ViewClassDependency) { if (dep instanceof ViewClassDependency) {
let vfd = <ViewClassDependency>dep; 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.reference = depTemplate.proxyViewClass;
vfd.placeholder.name = `View_${vfd.comp.name}`; vfd.placeholder.name = `View_${vfd.comp.name}`;
} else if (dep instanceof ComponentFactoryDependency) { } else if (dep instanceof ComponentFactoryDependency) {
let cfd = <ComponentFactoryDependency>dep; 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.reference = depTemplate.proxyComponentFactory;
cfd.placeholder.name = `compFactory_${cfd.comp.name}`; cfd.placeholder.name = `compFactory_${cfd.comp.name}`;
} else if (dep instanceof DirectiveWrapperDependency) { } else if (dep instanceof DirectiveWrapperDependency) {
@ -361,29 +348,12 @@ class CompiledTemplate {
private _viewClass: Function = null; private _viewClass: Function = null;
proxyViewClass: Type<any>; proxyViewClass: Type<any>;
proxyComponentFactory: ComponentFactory<any>; proxyComponentFactory: ComponentFactory<any>;
loading: Promise<any> = null;
private _normalizedCompMeta: CompileDirectiveMetadata = null;
isCompiled = false; isCompiled = false;
isCompiledWithDeps = false;
viewComponentTypes: Type<any>[] = [];
viewDirectives: CompileDirectiveMetadata[] = [];
viewPipes: CompilePipeMetadata[];
schemas: SchemaMetadata[];
constructor( constructor(
public isHost: boolean, selector: string, public compType: CompileIdentifierMetadata, public isHost: boolean, selector: string, public compType: CompileIdentifierMetadata,
public ngModule: CompileNgModuleMetadata, public compMeta: CompileDirectiveMetadata, public ngModule: CompileNgModuleMetadata,
viewDirectiveAndComponents: CompileDirectiveMetadata[], public directives: CompileIdentifierMetadata[]) {
_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);
}
});
const self = this; const self = this;
this.proxyViewClass = <any>function() { this.proxyViewClass = <any>function() {
if (!self._viewClass) { if (!self._viewClass) {
@ -395,21 +365,6 @@ class CompiledTemplate {
this.proxyComponentFactory = isHost ? this.proxyComponentFactory = isHost ?
new ComponentFactory<any>(selector, this.proxyViewClass, compType.reference) : new ComponentFactory<any>(selector, this.proxyViewClass, compType.reference) :
null; 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) { compiled(viewClass: Function) {
@ -417,8 +372,6 @@ class CompiledTemplate {
this.proxyViewClass.prototype = viewClass.prototype; this.proxyViewClass.prototype = viewClass.prototype;
this.isCompiled = true; this.isCompiled = true;
} }
depsCompiled() { this.isCompiledWithDeps = true; }
} }
function assertComponent(meta: CompileDirectiveMetadata) { function assertComponent(meta: CompileDirectiveMetadata) {

View File

@ -18,79 +18,78 @@ import {AsyncTestCompleter, beforeEach, describe, expect, inject, it} from '@ang
import {SpyResourceLoader} from './spies'; 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() { export function main() {
describe('DirectiveNormalizer', () => { describe('DirectiveNormalizer', () => {
var dirType: CompileTypeMetadata;
var dirTypeWithHttpUrl: CompileTypeMetadata;
beforeEach(() => { TestBed.configureCompiler({providers: TEST_COMPILER_PROVIDERS}); }); 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', () => { describe('normalizeDirective', () => {
it('should throw if no template was specified', it('should throw if no template was specified',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
expect(() => normalizer.normalizeDirective(new CompileDirectiveMetadata({ expect(() => normalizer.normalizeTemplate({
type: dirType, componentType: SomeComp,
isComponent: true, moduleUrl: SOME_MODULE_URL,
template: })).toThrowError('No template specified for component SomeComp');
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []})
}))).toThrowError('No template specified for component SomeComp');
})); }));
}); });
describe('normalizeTemplateSync', () => { describe('normalizeTemplateSync', () => {
it('should store the template', it('should store the template',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
let template = normalizer.normalizeTemplateSync(dirType, new CompileTemplateMetadata({ let template = normalizer.normalizeTemplateSync({
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null, encapsulation: null,
template: 'a', template: 'a',
templateUrl: null, templateUrl: null,
styles: [], styles: [],
styleUrls: [] styleUrls: []
})); });
expect(template.template).toEqual('a'); expect(template.template).toEqual('a');
expect(template.templateUrl).toEqual('package:some/module/a.js'); expect(template.templateUrl).toEqual('package:some/module/a.js');
})); }));
it('should resolve styles on the annotation against the moduleUrl', it('should resolve styles on the annotation against the moduleUrl',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
let template = normalizer.normalizeTemplateSync(dirType, new CompileTemplateMetadata({ let template = normalizer.normalizeTemplateSync({
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null, encapsulation: null,
template: '', template: '',
templateUrl: null, templateUrl: null,
styles: [], styles: [],
styleUrls: ['test.css'] styleUrls: ['test.css']
})); });
expect(template.styleUrls).toEqual(['package:some/module/test.css']); expect(template.styleUrls).toEqual(['package:some/module/test.css']);
})); }));
it('should resolve styles in the template against the moduleUrl', it('should resolve styles in the template against the moduleUrl',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
let template = let template = normalizer.normalizeTemplateSync({
normalizer.normalizeTemplateSync(dirType, new CompileTemplateMetadata({ componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null, encapsulation: null,
template: '<style>@import test.css</style>', template: '<style>@import test.css</style>',
templateUrl: null, templateUrl: null,
styles: [], styles: [],
styleUrls: [] styleUrls: []
})); });
expect(template.styleUrls).toEqual(['package:some/module/test.css']); expect(template.styleUrls).toEqual(['package:some/module/test.css']);
})); }));
it('should use ViewEncapsulation.Emulated by default', it('should use ViewEncapsulation.Emulated by default',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
let template = normalizer.normalizeTemplateSync(dirType, new CompileTemplateMetadata({ let template = normalizer.normalizeTemplateSync({
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null, encapsulation: null,
template: '', template: '',
templateUrl: null, templateUrl: null,
styles: [], styles: [],
styleUrls: ['test.css'] styleUrls: ['test.css']
})); });
expect(template.encapsulation).toEqual(ViewEncapsulation.Emulated); expect(template.encapsulation).toEqual(ViewEncapsulation.Emulated);
})); }));
@ -99,14 +98,15 @@ export function main() {
[CompilerConfig, DirectiveNormalizer], [CompilerConfig, DirectiveNormalizer],
(config: CompilerConfig, normalizer: DirectiveNormalizer) => { (config: CompilerConfig, normalizer: DirectiveNormalizer) => {
config.defaultEncapsulation = ViewEncapsulation.None; config.defaultEncapsulation = ViewEncapsulation.None;
let template = let template = normalizer.normalizeTemplateSync({
normalizer.normalizeTemplateSync(dirType, new CompileTemplateMetadata({ componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null, encapsulation: null,
template: '', template: '',
templateUrl: null, templateUrl: null,
styles: [], styles: [],
styleUrls: ['test.css'] styleUrls: ['test.css']
})); });
expect(template.encapsulation).toEqual(ViewEncapsulation.None); expect(template.encapsulation).toEqual(ViewEncapsulation.None);
})); }));
}); });
@ -120,13 +120,15 @@ export function main() {
resourceLoader: MockResourceLoader) => { resourceLoader: MockResourceLoader) => {
resourceLoader.expect('package:some/module/sometplurl.html', 'a'); resourceLoader.expect('package:some/module/sometplurl.html', 'a');
normalizer normalizer
.normalizeTemplateAsync(dirType, new CompileTemplateMetadata({ .normalizeTemplateAsync({
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null, encapsulation: null,
template: null, template: null,
templateUrl: 'sometplurl.html', templateUrl: 'sometplurl.html',
styles: [], styles: [],
styleUrls: ['test.css'] styleUrls: ['test.css']
})) })
.then((template: CompileTemplateMetadata) => { .then((template: CompileTemplateMetadata) => {
expect(template.template).toEqual('a'); expect(template.template).toEqual('a');
expect(template.templateUrl).toEqual('package:some/module/sometplurl.html'); expect(template.templateUrl).toEqual('package:some/module/sometplurl.html');
@ -142,13 +144,15 @@ export function main() {
resourceLoader: MockResourceLoader) => { resourceLoader: MockResourceLoader) => {
resourceLoader.expect('package:some/module/tpl/sometplurl.html', ''); resourceLoader.expect('package:some/module/tpl/sometplurl.html', '');
normalizer normalizer
.normalizeTemplateAsync(dirType, new CompileTemplateMetadata({ .normalizeTemplateAsync({
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null, encapsulation: null,
template: null, template: null,
templateUrl: 'tpl/sometplurl.html', templateUrl: 'tpl/sometplurl.html',
styles: [], styles: [],
styleUrls: ['test.css'] styleUrls: ['test.css']
})) })
.then((template: CompileTemplateMetadata) => { .then((template: CompileTemplateMetadata) => {
expect(template.styleUrls).toEqual(['package:some/module/test.css']); expect(template.styleUrls).toEqual(['package:some/module/test.css']);
async.done(); async.done();
@ -164,13 +168,15 @@ export function main() {
resourceLoader.expect( resourceLoader.expect(
'package:some/module/tpl/sometplurl.html', '<style>@import test.css</style>'); 'package:some/module/tpl/sometplurl.html', '<style>@import test.css</style>');
normalizer normalizer
.normalizeTemplateAsync(dirType, new CompileTemplateMetadata({ .normalizeTemplateAsync({
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null, encapsulation: null,
template: null, template: null,
templateUrl: 'tpl/sometplurl.html', templateUrl: 'tpl/sometplurl.html',
styles: [], styles: [],
styleUrls: [] styleUrls: []
})) })
.then((template: CompileTemplateMetadata) => { .then((template: CompileTemplateMetadata) => {
expect(template.styleUrls).toEqual(['package:some/module/tpl/test.css']); expect(template.styleUrls).toEqual(['package:some/module/tpl/test.css']);
async.done(); async.done();
@ -249,13 +255,15 @@ export function main() {
(async: AsyncTestCompleter, normalizer: DirectiveNormalizer, (async: AsyncTestCompleter, normalizer: DirectiveNormalizer,
resourceLoader: MockResourceLoader) => { resourceLoader: MockResourceLoader) => {
resourceLoader.expect('package:some/module/cmp.html', 'a'); resourceLoader.expect('package:some/module/cmp.html', 'a');
var templateMeta = new CompileTemplateMetadata({ var prenormMeta = {
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
templateUrl: 'cmp.html', templateUrl: 'cmp.html',
}); };
Promise Promise
.all([ .all([
normalizer.normalizeTemplateAsync(dirType, templateMeta), normalizer.normalizeTemplateAsync(prenormMeta),
normalizer.normalizeTemplateAsync(dirType, templateMeta) normalizer.normalizeTemplateAsync(prenormMeta)
]) ])
.then((templates: CompileTemplateMetadata[]) => { .then((templates: CompileTemplateMetadata[]) => {
expect(templates[0].template).toEqual('a'); expect(templates[0].template).toEqual('a');
@ -273,8 +281,13 @@ export function main() {
var viewEncapsulation = ViewEncapsulation.Native; var viewEncapsulation = ViewEncapsulation.Native;
var template = normalizer.normalizeLoadedTemplate( var template = normalizer.normalizeLoadedTemplate(
dirType, new CompileTemplateMetadata( {
{encapsulation: viewEncapsulation, styles: [], styleUrls: []}), componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: viewEncapsulation,
styles: [],
styleUrls: []
},
'', 'package:some/module/'); '', 'package:some/module/');
expect(template.encapsulation).toBe(viewEncapsulation); expect(template.encapsulation).toBe(viewEncapsulation);
})); }));
@ -282,17 +295,27 @@ export function main() {
it('should keep the template as html', it('should keep the template as html',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate( var template = normalizer.normalizeLoadedTemplate(
dirType, {
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}), 'a', componentType: SomeComp,
'package:some/module/'); moduleUrl: SOME_MODULE_URL,
encapsulation: null,
styles: [],
styleUrls: []
},
'a', 'package:some/module/');
expect(template.template).toEqual('a'); expect(template.template).toEqual('a');
})); }));
it('should collect ngContent', it('should collect ngContent',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate( 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/'); '<ng-content select="a"></ng-content>', 'package:some/module/');
expect(template.ngContentSelectors).toEqual(['a']); expect(template.ngContentSelectors).toEqual(['a']);
})); }));
@ -300,8 +323,13 @@ export function main() {
it('should normalize ngContent wildcard selector', it('should normalize ngContent wildcard selector',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate( 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>', '<ng-content></ng-content><ng-content select></ng-content><ng-content select="*"></ng-content>',
'package:some/module/'); 'package:some/module/');
expect(template.ngContentSelectors).toEqual(['*', '*', '*']); expect(template.ngContentSelectors).toEqual(['*', '*', '*']);
@ -310,8 +338,13 @@ export function main() {
it('should collect top level styles in the template', it('should collect top level styles in the template',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate( 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/'); '<style>a</style>', 'package:some/module/');
expect(template.styles).toEqual(['a']); expect(template.styles).toEqual(['a']);
})); }));
@ -319,8 +352,13 @@ export function main() {
it('should collect styles inside in elements', it('should collect styles inside in elements',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate( 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/'); '<div><style>a</style></div>', 'package:some/module/');
expect(template.styles).toEqual(['a']); expect(template.styles).toEqual(['a']);
})); }));
@ -328,8 +366,13 @@ export function main() {
it('should collect styleUrls in the template', it('should collect styleUrls in the template',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate( 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/'); '<link rel="stylesheet" href="aUrl">', 'package:some/module/');
expect(template.styleUrls).toEqual(['package:some/module/aUrl']); expect(template.styleUrls).toEqual(['package:some/module/aUrl']);
})); }));
@ -337,8 +380,13 @@ export function main() {
it('should collect styleUrls in elements', it('should collect styleUrls in elements',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate( 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/'); '<div><link rel="stylesheet" href="aUrl"></div>', 'package:some/module/');
expect(template.styleUrls).toEqual(['package:some/module/aUrl']); 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', it('should ignore link elements with non stylesheet rel attribute',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate( 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/'); '<link href="b" rel="a">', 'package:some/module/');
expect(template.styleUrls).toEqual([]); expect(template.styleUrls).toEqual([]);
})); }));
@ -355,8 +408,13 @@ export function main() {
it('should ignore link elements with absolute urls but non package: scheme', it('should ignore link elements with absolute urls but non package: scheme',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate( 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/'); '<link href="http://some/external.css" rel="stylesheet">', 'package:some/module/');
expect(template.styleUrls).toEqual([]); expect(template.styleUrls).toEqual([]);
})); }));
@ -364,8 +422,13 @@ export function main() {
it('should extract @import style urls into styleAbsUrl', it('should extract @import style urls into styleAbsUrl',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate( 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'); '', 'package:some/module/id');
expect(template.styles).toEqual(['']); expect(template.styles).toEqual(['']);
expect(template.styleUrls).toEqual(['package:some/module/test.css']); 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', it('should not resolve relative urls in inline styles',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate( var template = normalizer.normalizeLoadedTemplate(
dirType, new CompileTemplateMetadata({ {
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null, encapsulation: null,
styles: ['.foo{background-image: url(\'double.jpg\');'], styles: ['.foo{background-image: url(\'double.jpg\');'],
styleUrls: [] styleUrls: []
}), },
'', 'package:some/module/id'); '', 'package:some/module/id');
expect(template.styles).toEqual(['.foo{background-image: url(\'double.jpg\');']); 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', it('should resolve relative style urls in styleUrls',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate( 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'); '', 'package:some/module/id');
expect(template.styles).toEqual([]); expect(template.styles).toEqual([]);
expect(template.styleUrls).toEqual(['package:some/module/test.css']); 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', it('should resolve relative style urls in styleUrls with http directive url',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate( 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'); '', 'http://some/module/id');
expect(template.styles).toEqual([]); expect(template.styles).toEqual([]);
expect(template.styleUrls).toEqual(['http://some/module/test.css']); 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', it('should normalize ViewEncapsulation.Emulated to ViewEncapsulation.None if there are no styles nor stylesheets',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate( 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'); '', 'package:some/module/id');
expect(template.encapsulation).toEqual(ViewEncapsulation.None); expect(template.encapsulation).toEqual(ViewEncapsulation.None);
})); }));
@ -415,8 +495,13 @@ export function main() {
it('should ignore ng-content in elements with ngNonBindable', it('should ignore ng-content in elements with ngNonBindable',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate( 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>', '<div ngNonBindable><ng-content select="a"></ng-content></div>',
'package:some/module/'); 'package:some/module/');
expect(template.ngContentSelectors).toEqual([]); expect(template.ngContentSelectors).toEqual([]);
@ -425,8 +510,13 @@ export function main() {
it('should still collect <style> in elements with ngNonBindable', it('should still collect <style> in elements with ngNonBindable',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
var template = normalizer.normalizeLoadedTemplate( 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/'); '<div ngNonBindable><style>div {color:red}</style></div>', 'package:some/module/');
expect(template.styles).toEqual(['div {color:red}']); 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 {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 {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 {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 {stringify} from '../src/facade/lang';
import {CompileMetadataResolver} from '../src/metadata_resolver'; 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'; import {MalformedStylesComponent} from './metadata_resolver_fixture';
@ -20,15 +22,35 @@ export function main() {
describe('CompileMetadataResolver', () => { describe('CompileMetadataResolver', () => {
beforeEach(() => { TestBed.configureCompiler({providers: TEST_COMPILER_PROVIDERS}); }); beforeEach(() => { TestBed.configureCompiler({providers: TEST_COMPILER_PROVIDERS}); });
describe('getDirectiveMetadata', () => { it('should throw on the get... methods if the module has not been loaded yet',
it('should read metadata',
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => { 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.selector).toEqual('someSelector');
expect(meta.exportAs).toEqual('someExportAs'); expect(meta.exportAs).toEqual('someExportAs');
expect(meta.isComponent).toBe(true); expect(meta.isComponent).toBe(true);
expect(meta.type.reference).toBe(ComponentWithEverything); expect(meta.type.reference).toBe(ComponentWithEverythingInline);
expect(meta.type.name).toEqual(stringify(ComponentWithEverything)); expect(meta.type.name).toEqual(stringify(ComponentWithEverythingInline));
expect(meta.type.lifecycleHooks).toEqual(LIFECYCLE_HOOKS_VALUES); expect(meta.type.lifecycleHooks).toEqual(LIFECYCLE_HOOKS_VALUES);
expect(meta.changeDetection).toBe(ChangeDetectionStrategy.Default); expect(meta.changeDetection).toBe(ChangeDetectionStrategy.Default);
expect(meta.inputs).toEqual({'someProp': 'someProp'}); expect(meta.inputs).toEqual({'someProp': 'someProp'});
@ -38,14 +60,67 @@ export function main() {
expect(meta.hostAttributes).toEqual({'someHostAttr': 'someHostAttrValue'}); expect(meta.hostAttributes).toEqual({'someHostAttr': 'someHostAttrValue'});
expect(meta.template.encapsulation).toBe(ViewEncapsulation.Emulated); expect(meta.template.encapsulation).toBe(ViewEncapsulation.Emulated);
expect(meta.template.styles).toEqual(['someStyle']); expect(meta.template.styles).toEqual(['someStyle']);
expect(meta.template.styleUrls).toEqual(['someStyleUrl']);
expect(meta.template.template).toEqual('someTemplate'); expect(meta.template.template).toEqual('someTemplate');
expect(meta.template.templateUrl).toEqual('someTemplateUrl');
expect(meta.template.interpolation).toEqual(['{{', '}}']); 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', it('should use the moduleUrl from the reflector if none is given',
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => { inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
@NgModule({declarations: [ComponentWithoutModuleId]})
class SomeModule {
}
resolver.loadNgModuleMetadata(SomeModule, true);
const value: string = const value: string =
resolver.getDirectiveMetadata(ComponentWithoutModuleId).type.moduleUrl; resolver.getDirectiveMetadata(ComponentWithoutModuleId).type.moduleUrl;
const expectedEndValue = './ComponentWithoutModuleId'; const expectedEndValue = './ComponentWithoutModuleId';
@ -54,7 +129,11 @@ export function main() {
it('should throw when the moduleId is not a string', it('should throw when the moduleId is not a string',
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => { inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
expect(() => resolver.getDirectiveMetadata(ComponentWithInvalidModuleId)) @NgModule({declarations: [ComponentWithInvalidModuleId]})
class SomeModule {
}
expect(() => resolver.loadNgModuleMetadata(SomeModule, true))
.toThrowError( .toThrowError(
`moduleId should be a string in "ComponentWithInvalidModuleId". See` + `moduleId should be a string in "ComponentWithInvalidModuleId". See` +
` https://goo.gl/wIDDiL for more information.\n` + ` https://goo.gl/wIDDiL for more information.\n` +
@ -65,13 +144,21 @@ export function main() {
it('should throw when metadata is incorrectly typed', it('should throw when metadata is incorrectly typed',
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => { 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.`); .toThrowError(`Expected 'styles' to be an array of strings.`);
})); }));
it('should throw with descriptive error message when provider token can not be resolved', it('should throw with descriptive error message when provider token can not be resolved',
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => { 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: (?).`); .toThrowError(`Can't resolve all parameters for MyBrokenComp1: (?).`);
})); }));
it('should throw with descriptive error message when a directive is passed to imports', it('should throw with descriptive error message when a directive is passed to imports',
@ -79,7 +166,7 @@ export function main() {
@NgModule({imports: [ComponentWithoutModuleId]}) @NgModule({imports: [ComponentWithoutModuleId]})
class ModuleWithImportedComponent { class ModuleWithImportedComponent {
} }
expect(() => resolver.getNgModuleMetadata(ModuleWithImportedComponent)) expect(() => resolver.loadNgModuleMetadata(ModuleWithImportedComponent, true))
.toThrowError( .toThrowError(
`Unexpected directive 'ComponentWithoutModuleId' imported by the module 'ModuleWithImportedComponent'`); `Unexpected directive 'ComponentWithoutModuleId' imported by the module 'ModuleWithImportedComponent'`);
})); }));
@ -92,7 +179,7 @@ export function main() {
@NgModule({imports: [SomePipe]}) @NgModule({imports: [SomePipe]})
class ModuleWithImportedPipe { class ModuleWithImportedPipe {
} }
expect(() => resolver.getNgModuleMetadata(ModuleWithImportedPipe)) expect(() => resolver.loadNgModuleMetadata(ModuleWithImportedPipe, true))
.toThrowError( .toThrowError(
`Unexpected pipe 'SomePipe' imported by the module 'ModuleWithImportedPipe'`); `Unexpected pipe 'SomePipe' imported by the module 'ModuleWithImportedPipe'`);
})); }));
@ -105,7 +192,7 @@ export function main() {
@NgModule({declarations: [SomeModule]}) @NgModule({declarations: [SomeModule]})
class ModuleWithDeclaredModule { class ModuleWithDeclaredModule {
} }
expect(() => resolver.getNgModuleMetadata(ModuleWithDeclaredModule)) expect(() => resolver.loadNgModuleMetadata(ModuleWithDeclaredModule, true))
.toThrowError( .toThrowError(
`Unexpected module 'SomeModule' declared by the module 'ModuleWithDeclaredModule'`); `Unexpected module 'SomeModule' declared by the module 'ModuleWithDeclaredModule'`);
})); }));
@ -115,7 +202,7 @@ export function main() {
@NgModule({declarations: [null]}) @NgModule({declarations: [null]})
class ModuleWithNullDeclared { class ModuleWithNullDeclared {
} }
expect(() => resolver.getNgModuleMetadata(ModuleWithNullDeclared)) expect(() => resolver.loadNgModuleMetadata(ModuleWithNullDeclared, true))
.toThrowError( .toThrowError(
`Unexpected value 'null' declared by the module 'ModuleWithNullDeclared'`); `Unexpected value 'null' declared by the module 'ModuleWithNullDeclared'`);
})); }));
@ -125,7 +212,7 @@ export function main() {
@NgModule({imports: [null]}) @NgModule({imports: [null]})
class ModuleWithNullImported { class ModuleWithNullImported {
} }
expect(() => resolver.getNgModuleMetadata(ModuleWithNullImported)) expect(() => resolver.loadNgModuleMetadata(ModuleWithNullImported, true))
.toThrowError( .toThrowError(
`Unexpected value 'null' imported by the module 'ModuleWithNullImported'`); `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', it('should throw with descriptive error message when a param token of a dependency is undefined',
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => { 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: (?).`); .toThrowError(`Can't resolve all parameters for NonAnnotatedService: (?).`);
})); }));
it('should throw with descriptive error message when one of providers is not present', it('should throw with descriptive error message when one of providers is not present',
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => { inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
expect(() => resolver.getDirectiveMetadata(MyBrokenComp3)) @NgModule({declarations: [MyBrokenComp3]})
class SomeModule {
}
expect(() => resolver.loadNgModuleMetadata(SomeModule, true))
.toThrowError( .toThrowError(
`Invalid providers for "MyBrokenComp3" - only instances of Provider and Type are allowed, got: [SimpleService, ?null?, ...]`); `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', it('should throw with descriptive error message when one of viewProviders is not present',
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => { inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
expect(() => resolver.getDirectiveMetadata(MyBrokenComp4)) @NgModule({declarations: [MyBrokenComp4]})
class SomeModule {
}
expect(() => resolver.loadNgModuleMetadata(SomeModule, true))
.toThrowError( .toThrowError(
`Invalid viewProviders for "MyBrokenComp4" - only instances of Provider and Type are allowed, got: [?null?, ...]`); `Invalid viewProviders for "MyBrokenComp4" - only instances of Provider and Type are allowed, got: [?null?, ...]`);
})); }));
@ -160,25 +259,49 @@ export function main() {
class ModuleWithUndefinedBootstrap { class ModuleWithUndefinedBootstrap {
} }
expect(() => resolver.getNgModuleMetadata(ModuleWithNullBootstrap)) expect(() => resolver.loadNgModuleMetadata(ModuleWithNullBootstrap, true))
.toThrowError( .toThrowError(
`Unexpected value 'null' used in the bootstrap property of module 'ModuleWithNullBootstrap'`); `Unexpected value 'null' used in the bootstrap property of module 'ModuleWithNullBootstrap'`);
expect(() => resolver.getNgModuleMetadata(ModuleWithUndefinedBootstrap)) expect(() => resolver.loadNgModuleMetadata(ModuleWithUndefinedBootstrap, true))
.toThrowError( .toThrowError(
`Unexpected value 'undefined' used in the bootstrap property of module 'ModuleWithUndefinedBootstrap'`); `Unexpected value 'undefined' used in the bootstrap property of module 'ModuleWithUndefinedBootstrap'`);
})); }));
it('should throw an error when the interpolation config has invalid symbols', it('should throw an error when the interpolation config has invalid symbols',
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => { inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
expect(() => resolver.getDirectiveMetadata(ComponentWithInvalidInterpolation1)) @NgModule({declarations: [ComponentWithInvalidInterpolation1]})
class Module1 {
}
expect(() => resolver.loadNgModuleMetadata(Module1, true))
.toThrowError(`[' ', ' '] contains unusable interpolation symbol.`); .toThrowError(`[' ', ' '] contains unusable interpolation symbol.`);
expect(() => resolver.getDirectiveMetadata(ComponentWithInvalidInterpolation2))
@NgModule({declarations: [ComponentWithInvalidInterpolation2]})
class Module2 {
}
expect(() => resolver.loadNgModuleMetadata(Module2, true))
.toThrowError(`['{', '}'] contains unusable interpolation symbol.`); .toThrowError(`['{', '}'] contains unusable interpolation symbol.`);
expect(() => resolver.getDirectiveMetadata(ComponentWithInvalidInterpolation3))
@NgModule({declarations: [ComponentWithInvalidInterpolation3]})
class Module3 {
}
expect(() => resolver.loadNgModuleMetadata(Module3, true))
.toThrowError(`['<%', '%>'] contains unusable interpolation symbol.`); .toThrowError(`['<%', '%>'] contains unusable interpolation symbol.`);
expect(() => resolver.getDirectiveMetadata(ComponentWithInvalidInterpolation4))
@NgModule({declarations: [ComponentWithInvalidInterpolation4]})
class Module4 {
}
expect(() => resolver.loadNgModuleMetadata(Module4, true))
.toThrowError(`['&#', '}}'] contains unusable interpolation symbol.`); .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.`); .toThrowError(`['&lbrace;', '}}'] contains unusable interpolation symbol.`);
})); }));
}); });
@ -194,12 +317,10 @@ export function main() {
class MyModule { class MyModule {
} }
const modMeta = resolver.getNgModuleMetadata(MyModule); const modMeta = resolver.loadNgModuleMetadata(MyModule, true).ngModule;
expect(modMeta.declaredDirectives.length).toBe(1); 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: ''}) @Component({selector: 'someComponent', template: ''})
@ -210,6 +331,14 @@ class ComponentWithoutModuleId {
class ComponentWithInvalidModuleId { class ComponentWithInvalidModuleId {
} }
@Component({
selector: 'someSelector',
templateUrl: 'someTemplateUrl',
styleUrls: ['someStyleUrl'],
})
class ComponentWithExternalResources {
}
@Component({ @Component({
selector: 'someSelector', selector: 'someSelector',
inputs: ['someProp'], inputs: ['someProp'],
@ -223,13 +352,11 @@ class ComponentWithInvalidModuleId {
moduleId: 'someModuleId', moduleId: 'someModuleId',
changeDetection: ChangeDetectionStrategy.Default, changeDetection: ChangeDetectionStrategy.Default,
template: 'someTemplate', template: 'someTemplate',
templateUrl: 'someTemplateUrl',
encapsulation: ViewEncapsulation.Emulated, encapsulation: ViewEncapsulation.Emulated,
styles: ['someStyle'], styles: ['someStyle'],
styleUrls: ['someStyleUrl'],
interpolation: ['{{', '}}'] interpolation: ['{{', '}}']
}) })
class ComponentWithEverything implements OnChanges, class ComponentWithEverythingInline implements OnChanges,
OnInit, DoCheck, OnDestroy, AfterContentInit, AfterContentChecked, AfterViewInit, OnInit, DoCheck, OnDestroy, AfterContentInit, AfterContentChecked, AfterViewInit,
AfterViewChecked { AfterViewChecked {
ngOnChanges(changes: SimpleChanges): void {} ngOnChanges(changes: SimpleChanges): void {}

View File

@ -8,8 +8,7 @@
import {DirectiveResolver, ResourceLoader} from '@angular/compiler'; import {DirectiveResolver, ResourceLoader} from '@angular/compiler';
import {Compiler, Component, Injector, NgModule, NgModuleFactory} from '@angular/core'; import {Compiler, Component, Injector, NgModule, NgModuleFactory} from '@angular/core';
import {TestBed, async, fakeAsync, tick} from '@angular/core/testing'; import {TestBed, async, fakeAsync, inject, tick} from '@angular/core/testing';
import {beforeEach, describe, inject, it} from '@angular/core/testing/testing_internal';
import {expect} from '@angular/platform-browser/testing/matchers'; import {expect} from '@angular/platform-browser/testing/matchers';
import {stringify} from '../src/facade/lang'; 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 {Console} from '@angular/core/src/console';
import {ComponentRef} from '@angular/core/src/linker/component_factory'; import {ComponentRef} from '@angular/core/src/linker/component_factory';
import {Testability, TestabilityRegistry} from '@angular/core/src/testability/testability'; 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 {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
@ -149,11 +149,19 @@ export function main() {
afterEach(destroyPlatform); afterEach(destroyPlatform);
it('should throw if bootstrapped Directive is not a Component', () => { it('should throw if bootstrapped Directive is not a Component',
expect(() => bootstrap(HelloRootDirectiveIsNotCmp)) inject([AsyncTestCompleter], (done: AsyncTestCompleter) => {
.toThrowError( 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.`); `Could not compile '${stringify(HelloRootDirectiveIsNotCmp)}' because it is not a component.`);
done.done();
}); });
}));
it('should throw if no element is found', it('should throw if no element is found',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {