feat(compiler): support sync runtime compile

Adds new abstraction `Compiler` with methods
`compileComponentAsync` and `compileComponentSync`.
This is in preparation of deprecating `ComponentResolver`.

`compileComponentSync` is able to compile components
synchronously given all components either have an inline
template or they have been compiled before.

Also changes `TestComponentBuilder.createSync` to
take a `Type` and use the new `compileComponentSync` method.

Also supports overriding the component metadata even if
the component has already been compiled.

Also fixes #7084 in a better way.

BREAKING CHANGE:
`TestComponentBuilder.createSync` now takes a component type
and throws if not all templates are either inlined
are compiled before via `createAsync`.

Closes #9594
This commit is contained in:
Tobias Bosch 2016-06-24 08:46:43 -07:00
parent 24eb8389d2
commit bf598d6b8b
35 changed files with 1093 additions and 700 deletions

View File

@ -101,44 +101,23 @@ export class CodeGenerator {
return path.join(this.options.genDir, path.relative(root, filePath)); return path.join(this.options.genDir, path.relative(root, filePath));
} }
// TODO(tbosch): add a cache for shared css files
// TODO(tbosch): detect cycles!
private generateStylesheet(filepath: string, shim: boolean): Promise<any> {
return this.compiler.loadAndCompileStylesheet(filepath, shim, '.ts')
.then((sourceWithImports) => {
const emitPath = this.calculateEmitPath(sourceWithImports.source.moduleUrl);
// TODO(alexeagle): should include the sourceFile to the WriteFileCallback
this.host.writeFile(emitPath, PREAMBLE + sourceWithImports.source.source, false);
return Promise.all(
sourceWithImports.importedUrls.map(url => this.generateStylesheet(url, shim)));
});
}
codegen(): Promise<any> { codegen(): Promise<any> {
let stylesheetPromises: Promise<any>[] = [];
const generateOneFile = (absSourcePath: string) => const generateOneFile = (absSourcePath: string) =>
Promise.all(this.readComponents(absSourcePath)) Promise.all(this.readComponents(absSourcePath))
.then((metadatas: compiler.CompileDirectiveMetadata[]) => { .then((metadatas: compiler.CompileDirectiveMetadata[]) => {
if (!metadatas || !metadatas.length) { if (!metadatas || !metadatas.length) {
return; return;
} }
metadatas.forEach((metadata) => {
let stylesheetPaths = metadata && metadata.template && metadata.template.styleUrls;
if (stylesheetPaths) {
stylesheetPaths.forEach((path) => {
stylesheetPromises.push(this.generateStylesheet(
path, metadata.template.encapsulation === ViewEncapsulation.Emulated));
});
}
});
return this.generateSource(metadatas); return this.generateSource(metadatas);
}) })
.then(generated => { .then(generatedModules => {
if (generated) { if (generatedModules) {
const sourceFile = this.program.getSourceFile(absSourcePath); generatedModules.forEach((generatedModule) => {
const emitPath = this.calculateEmitPath(generated.moduleUrl); const sourceFile = this.program.getSourceFile(absSourcePath);
this.host.writeFile( const emitPath = this.calculateEmitPath(generatedModule.moduleUrl);
emitPath, PREAMBLE + generated.source, false, () => {}, [sourceFile]); this.host.writeFile(
emitPath, PREAMBLE + generatedModule.source, false, () => {}, [sourceFile]);
});
} }
}) })
.catch((e) => { console.error(e.stack); }); .catch((e) => { console.error(e.stack); });
@ -146,7 +125,7 @@ export class CodeGenerator {
.map(sf => sf.fileName) .map(sf => sf.fileName)
.filter(f => !GENERATED_FILES.test(f)) .filter(f => !GENERATED_FILES.test(f))
.map(generateOneFile); .map(generateOneFile);
return Promise.all(stylesheetPromises.concat(compPromises)); return Promise.all(compPromises);
} }
static create( static create(
@ -173,7 +152,7 @@ export class CodeGenerator {
/*console*/ null, []); /*console*/ null, []);
const offlineCompiler = new compiler.OfflineCompiler( const offlineCompiler = new compiler.OfflineCompiler(
normalizer, tmplParser, new StyleCompiler(urlResolver), new ViewCompiler(config), normalizer, tmplParser, new StyleCompiler(urlResolver), new ViewCompiler(config),
new TypeScriptEmitter(reflectorHost), xhr); new TypeScriptEmitter(reflectorHost));
const resolver = new CompileMetadataResolver( const resolver = new CompileMetadataResolver(
new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector), new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector),
new compiler.ViewResolver(staticReflector), config, staticReflector); new compiler.ViewResolver(staticReflector), config, staticReflector);

View File

@ -162,7 +162,7 @@ class Extractor {
/*console*/ null, []); /*console*/ null, []);
const offlineCompiler = new compiler.OfflineCompiler( const offlineCompiler = new compiler.OfflineCompiler(
normalizer, tmplParser, new StyleCompiler(urlResolver), new ViewCompiler(config), normalizer, tmplParser, new StyleCompiler(urlResolver), new ViewCompiler(config),
new TypeScriptEmitter(reflectorHost), xhr); new TypeScriptEmitter(reflectorHost));
const resolver = new CompileMetadataResolver( const resolver = new CompileMetadataResolver(
new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector), new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector),
new compiler.ViewResolver(staticReflector), config, staticReflector); new compiler.ViewResolver(staticReflector), config, staticReflector);

View File

@ -600,6 +600,31 @@ export class CompileQueryMetadata {
} }
} }
/**
* Metadata about a stylesheet
*/
export class CompileStylesheetMetadata {
moduleUrl: string;
styles: string[];
styleUrls: string[];
constructor(
{moduleUrl, styles,
styleUrls}: {moduleUrl?: string, styles?: string[], styleUrls?: string[]} = {}) {
this.moduleUrl = moduleUrl;
this.styles = _normalizeArray(styles);
this.styleUrls = _normalizeArray(styleUrls);
}
static fromJson(data: {[key: string]: any}): CompileStylesheetMetadata {
return new CompileStylesheetMetadata(
{moduleUrl: data['moduleUrl'], styles: data['styles'], styleUrls: data['styleUrls']});
}
toJson(): {[key: string]: any} {
return {'moduleUrl': this.moduleUrl, 'styles': this.styles, 'styleUrls': this.styleUrls};
}
}
/** /**
* Metadata regarding compilation of a template. * Metadata regarding compilation of a template.
*/ */
@ -609,17 +634,19 @@ export class CompileTemplateMetadata {
templateUrl: string; templateUrl: string;
styles: string[]; styles: string[];
styleUrls: string[]; styleUrls: string[];
externalStylesheets: CompileStylesheetMetadata[];
animations: CompileAnimationEntryMetadata[]; animations: CompileAnimationEntryMetadata[];
ngContentSelectors: string[]; ngContentSelectors: string[];
interpolation: [string, string]; interpolation: [string, string];
constructor( constructor(
{encapsulation, template, templateUrl, styles, styleUrls, animations, ngContentSelectors, {encapsulation, template, templateUrl, styles, styleUrls, externalStylesheets, animations,
interpolation}: { ngContentSelectors, interpolation}: {
encapsulation?: ViewEncapsulation, encapsulation?: ViewEncapsulation,
template?: string, template?: string,
templateUrl?: string, templateUrl?: string,
styles?: string[], styles?: string[],
styleUrls?: string[], styleUrls?: string[],
externalStylesheets?: CompileStylesheetMetadata[],
ngContentSelectors?: string[], ngContentSelectors?: string[],
animations?: CompileAnimationEntryMetadata[], animations?: CompileAnimationEntryMetadata[],
interpolation?: [string, string] interpolation?: [string, string]
@ -627,8 +654,9 @@ export class CompileTemplateMetadata {
this.encapsulation = encapsulation; this.encapsulation = encapsulation;
this.template = template; this.template = template;
this.templateUrl = templateUrl; this.templateUrl = templateUrl;
this.styles = isPresent(styles) ? styles : []; this.styles = _normalizeArray(styles);
this.styleUrls = isPresent(styleUrls) ? styleUrls : []; this.styleUrls = _normalizeArray(styleUrls);
this.externalStylesheets = _normalizeArray(externalStylesheets);
this.animations = isPresent(animations) ? ListWrapper.flatten(animations) : []; this.animations = isPresent(animations) ? ListWrapper.flatten(animations) : [];
this.ngContentSelectors = isPresent(ngContentSelectors) ? ngContentSelectors : []; this.ngContentSelectors = isPresent(ngContentSelectors) ? ngContentSelectors : [];
if (isPresent(interpolation) && interpolation.length != 2) { if (isPresent(interpolation) && interpolation.length != 2) {
@ -648,6 +676,8 @@ export class CompileTemplateMetadata {
templateUrl: data['templateUrl'], templateUrl: data['templateUrl'],
styles: data['styles'], styles: data['styles'],
styleUrls: data['styleUrls'], styleUrls: data['styleUrls'],
externalStylesheets:
_arrayFromJson(data['externalStylesheets'], CompileStylesheetMetadata.fromJson),
animations: animations, animations: animations,
ngContentSelectors: data['ngContentSelectors'], ngContentSelectors: data['ngContentSelectors'],
interpolation: data['interpolation'] interpolation: data['interpolation']
@ -662,6 +692,7 @@ export class CompileTemplateMetadata {
'templateUrl': this.templateUrl, 'templateUrl': this.templateUrl,
'styles': this.styles, 'styles': this.styles,
'styleUrls': this.styleUrls, 'styleUrls': this.styleUrls,
'externalStylesheets': _objToJson(this.externalStylesheets),
'animations': _objToJson(this.animations), 'animations': _objToJson(this.animations),
'ngContentSelectors': this.ngContentSelectors, 'ngContentSelectors': this.ngContentSelectors,
'interpolation': this.interpolation 'interpolation': this.interpolation

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ComponentResolver, Type} from '@angular/core'; import {Compiler, ComponentResolver, Type} from '@angular/core';
export * from './template_ast'; export * from './template_ast';
export {TEMPLATE_TRANSFORMS} from './template_parser'; export {TEMPLATE_TRANSFORMS} from './template_parser';
@ -49,6 +49,7 @@ export const COMPILER_PROVIDERS: Array<any|Type|{[k: string]: any}|any[]> =
/*@ts2dart_Provider*/ {provide: CompilerConfig, useValue: new CompilerConfig()}, /*@ts2dart_Provider*/ {provide: CompilerConfig, useValue: new CompilerConfig()},
RuntimeCompiler, RuntimeCompiler,
/*@ts2dart_Provider*/ {provide: ComponentResolver, useExisting: RuntimeCompiler}, /*@ts2dart_Provider*/ {provide: ComponentResolver, useExisting: RuntimeCompiler},
/*@ts2dart_Provider*/ {provide: Compiler, useExisting: RuntimeCompiler},
DomElementSchemaRegistry, DomElementSchemaRegistry,
/*@ts2dart_Provider*/ {provide: ElementSchemaRegistry, useExisting: DomElementSchemaRegistry}, /*@ts2dart_Provider*/ {provide: ElementSchemaRegistry, useExisting: DomElementSchemaRegistry},
UrlResolver, ViewResolver, DirectiveResolver, PipeResolver UrlResolver, ViewResolver, DirectiveResolver, PipeResolver

View File

@ -12,65 +12,92 @@ import {PromiseWrapper} from '../src/facade/async';
import {BaseException} from '../src/facade/exceptions'; import {BaseException} from '../src/facade/exceptions';
import {isBlank, isPresent} from '../src/facade/lang'; import {isBlank, isPresent} from '../src/facade/lang';
import {CompileTypeMetadata, CompileDirectiveMetadata, CompileTemplateMetadata,} from './compile_metadata'; import {CompileDirectiveMetadata, CompileStylesheetMetadata, CompileTemplateMetadata, CompileTypeMetadata} from './compile_metadata';
import {XHR} from './xhr';
import {UrlResolver} from './url_resolver';
import {extractStyleUrls, isStyleUrlResolvable} from './style_url_resolver';
import {HtmlAstVisitor, HtmlElementAst, HtmlTextAst, HtmlAttrAst, HtmlCommentAst, HtmlExpansionAst, HtmlExpansionCaseAst, htmlVisitAll} from './html_ast';
import {HtmlParser} from './html_parser';
import {CompilerConfig} from './config'; import {CompilerConfig} from './config';
import {HtmlAstVisitor, HtmlAttrAst, HtmlCommentAst, HtmlElementAst, HtmlExpansionAst, HtmlExpansionCaseAst, HtmlTextAst, htmlVisitAll} from './html_ast';
import {HtmlParser} from './html_parser';
import {extractStyleUrls, isStyleUrlResolvable} from './style_url_resolver';
import {PreparsedElementType, preparseElement} from './template_preparser';
import {UrlResolver} from './url_resolver';
import {XHR} from './xhr';
import {preparseElement, PreparsedElementType} from './template_preparser'; export class NormalizeDirectiveResult {
constructor(
public syncResult: CompileDirectiveMetadata,
public asyncResult: Promise<CompileDirectiveMetadata>) {}
}
@Injectable() @Injectable()
export class DirectiveNormalizer { export class DirectiveNormalizer {
private _xhrCache = new Map<string, Promise<string>>();
constructor( constructor(
private _xhr: XHR, private _urlResolver: UrlResolver, private _htmlParser: HtmlParser, private _xhr: XHR, private _urlResolver: UrlResolver, private _htmlParser: HtmlParser,
private _config: CompilerConfig) {} private _config: CompilerConfig) {}
normalizeDirective(directive: CompileDirectiveMetadata): Promise<CompileDirectiveMetadata> { clearCache() { this._xhrCache.clear(); }
if (!directive.isComponent) {
// For non components there is nothing to be normalized yet. clearCacheFor(normalizedDirective: CompileDirectiveMetadata) {
return PromiseWrapper.resolve(directive); if (!normalizedDirective.isComponent) {
return;
} }
return this.normalizeTemplate(directive.type, directive.template) this._xhrCache.delete(normalizedDirective.template.templateUrl);
.then((normalizedTemplate: CompileTemplateMetadata) => new CompileDirectiveMetadata({ normalizedDirective.template.externalStylesheets.forEach(
type: directive.type, (stylesheet) => { this._xhrCache.delete(stylesheet.moduleUrl); });
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,
lifecycleHooks: directive.lifecycleHooks,
providers: directive.providers,
viewProviders: directive.viewProviders,
queries: directive.queries,
viewQueries: directive.viewQueries,
precompile: directive.precompile,
template: normalizedTemplate
}));
} }
normalizeTemplate(directiveType: CompileTypeMetadata, template: CompileTemplateMetadata): private _fetch(url: string): Promise<string> {
Promise<CompileTemplateMetadata> { var result = this._xhrCache.get(url);
if (isPresent(template.template)) { if (!result) {
return PromiseWrapper.resolve(this.normalizeLoadedTemplate( result = this._xhr.get(url);
directiveType, template, template.template, directiveType.moduleUrl)); this._xhrCache.set(url, result);
} else if (isPresent(template.templateUrl)) {
var sourceAbsUrl = this._urlResolver.resolve(directiveType.moduleUrl, template.templateUrl);
return this._xhr.get(sourceAbsUrl)
.then(
templateContent => this.normalizeLoadedTemplate(
directiveType, template, templateContent, sourceAbsUrl));
} else {
throw new BaseException(`No template specified for component ${directiveType.name}`);
} }
return result;
}
normalizeDirective(directive: CompileDirectiveMetadata): NormalizeDirectiveResult {
if (!directive.isComponent) {
// For non components there is nothing to be normalized yet.
return new NormalizeDirectiveResult(directive, Promise.resolve(directive));
}
let normalizedTemplateSync: CompileTemplateMetadata = null;
let normalizedTemplateAsync: Promise<CompileTemplateMetadata>;
if (isPresent(directive.template.template)) {
normalizedTemplateSync = this.normalizeTemplateSync(directive.type, directive.template);
normalizedTemplateAsync = Promise.resolve(normalizedTemplateSync);
} else if (directive.template.templateUrl) {
normalizedTemplateAsync = this.normalizeTemplateAsync(directive.type, directive.template);
} else {
throw new BaseException(`No template specified for component ${directive.type.name}`);
}
if (normalizedTemplateSync && normalizedTemplateSync.styleUrls.length === 0) {
// sync case
let normalizedDirective = _cloneDirectiveWithTemplate(directive, normalizedTemplateSync);
return new NormalizeDirectiveResult(
normalizedDirective, Promise.resolve(normalizedDirective));
} else {
// async case
return new NormalizeDirectiveResult(
null,
normalizedTemplateAsync
.then((normalizedTemplate) => this.normalizeExternalStylesheets(normalizedTemplate))
.then(
(normalizedTemplate) =>
_cloneDirectiveWithTemplate(directive, normalizedTemplate)));
}
}
normalizeTemplateSync(directiveType: CompileTypeMetadata, template: CompileTemplateMetadata):
CompileTemplateMetadata {
return this.normalizeLoadedTemplate(
directiveType, template, template.template, directiveType.moduleUrl);
}
normalizeTemplateAsync(directiveType: CompileTypeMetadata, template: CompileTemplateMetadata):
Promise<CompileTemplateMetadata> {
let templateUrl = this._urlResolver.resolve(directiveType.moduleUrl, template.templateUrl);
return this._fetch(templateUrl)
.then((value) => this.normalizeLoadedTemplate(directiveType, template, value, templateUrl));
} }
normalizeLoadedTemplate( normalizeLoadedTemplate(
@ -81,42 +108,87 @@ export class DirectiveNormalizer {
var errorString = rootNodesAndErrors.errors.join('\n'); var errorString = rootNodesAndErrors.errors.join('\n');
throw new BaseException(`Template parse errors:\n${errorString}`); throw new BaseException(`Template parse errors:\n${errorString}`);
} }
var templateMetadataStyles = this.normalizeStylesheet(new CompileStylesheetMetadata({
styles: templateMeta.styles,
styleUrls: templateMeta.styleUrls,
moduleUrl: directiveType.moduleUrl
}));
var visitor = new TemplatePreparseVisitor(); var visitor = new TemplatePreparseVisitor();
htmlVisitAll(visitor, rootNodesAndErrors.rootNodes); htmlVisitAll(visitor, rootNodesAndErrors.rootNodes);
var allStyles = templateMeta.styles.concat(visitor.styles); var templateStyles = this.normalizeStylesheet(new CompileStylesheetMetadata(
{styles: visitor.styles, styleUrls: visitor.styleUrls, moduleUrl: templateAbsUrl}));
var allStyleAbsUrls = var allStyles = templateMetadataStyles.styles.concat(templateStyles.styles);
visitor.styleUrls.filter(isStyleUrlResolvable) var allStyleUrls = templateMetadataStyles.styleUrls.concat(templateStyles.styleUrls);
.map(url => this._urlResolver.resolve(templateAbsUrl, url))
.concat(templateMeta.styleUrls.filter(isStyleUrlResolvable)
.map(url => this._urlResolver.resolve(directiveType.moduleUrl, url)));
var allResolvedStyles = allStyles.map(style => {
var styleWithImports = extractStyleUrls(this._urlResolver, templateAbsUrl, style);
styleWithImports.styleUrls.forEach(styleUrl => allStyleAbsUrls.push(styleUrl));
return styleWithImports.style;
});
var encapsulation = templateMeta.encapsulation; var encapsulation = templateMeta.encapsulation;
if (isBlank(encapsulation)) { if (isBlank(encapsulation)) {
encapsulation = this._config.defaultEncapsulation; encapsulation = this._config.defaultEncapsulation;
} }
if (encapsulation === ViewEncapsulation.Emulated && allResolvedStyles.length === 0 && if (encapsulation === ViewEncapsulation.Emulated && allStyles.length === 0 &&
allStyleAbsUrls.length === 0) { allStyleUrls.length === 0) {
encapsulation = ViewEncapsulation.None; encapsulation = ViewEncapsulation.None;
} }
return new CompileTemplateMetadata({ return new CompileTemplateMetadata({
encapsulation: encapsulation, encapsulation: encapsulation,
template: template, template: template,
templateUrl: templateAbsUrl, templateUrl: templateAbsUrl,
styles: allResolvedStyles, styles: allStyles,
styleUrls: allStyleAbsUrls, styleUrls: allStyleUrls,
externalStylesheets: templateMeta.externalStylesheets,
ngContentSelectors: visitor.ngContentSelectors, ngContentSelectors: visitor.ngContentSelectors,
animations: templateMeta.animations, animations: templateMeta.animations,
interpolation: templateMeta.interpolation interpolation: templateMeta.interpolation
}); });
} }
normalizeExternalStylesheets(templateMeta: CompileTemplateMetadata):
Promise<CompileTemplateMetadata> {
return this._loadMissingExternalStylesheets(templateMeta.styleUrls)
.then((externalStylesheets) => new CompileTemplateMetadata({
encapsulation: templateMeta.encapsulation,
template: templateMeta.template,
templateUrl: templateMeta.templateUrl,
styles: templateMeta.styles,
styleUrls: templateMeta.styleUrls,
externalStylesheets: externalStylesheets,
ngContentSelectors: templateMeta.ngContentSelectors,
animations: templateMeta.animations,
interpolation: templateMeta.interpolation
}));
}
private _loadMissingExternalStylesheets(
styleUrls: string[],
loadedStylesheets:
Map<string, CompileStylesheetMetadata> = new Map<string, CompileStylesheetMetadata>()):
Promise<CompileStylesheetMetadata[]> {
return Promise
.all(styleUrls.filter((styleUrl) => !loadedStylesheets.has(styleUrl))
.map(styleUrl => this._fetch(styleUrl).then((loadedStyle) => {
var stylesheet = this.normalizeStylesheet(
new CompileStylesheetMetadata({styles: [loadedStyle], moduleUrl: styleUrl}));
loadedStylesheets.set(styleUrl, stylesheet);
return this._loadMissingExternalStylesheets(
stylesheet.styleUrls, loadedStylesheets);
})))
.then((_) => Array.from(loadedStylesheets.values()));
}
normalizeStylesheet(stylesheet: CompileStylesheetMetadata): CompileStylesheetMetadata {
var allStyleUrls = stylesheet.styleUrls.filter(isStyleUrlResolvable)
.map(url => this._urlResolver.resolve(stylesheet.moduleUrl, url));
var allStyles = stylesheet.styles.map(style => {
var styleWithImports = extractStyleUrls(this._urlResolver, stylesheet.moduleUrl, style);
allStyleUrls.push(...styleWithImports.styleUrls);
return styleWithImports.style;
});
return new CompileStylesheetMetadata(
{styles: allStyles, styleUrls: allStyleUrls, moduleUrl: stylesheet.moduleUrl});
}
} }
class TemplatePreparseVisitor implements HtmlAstVisitor { class TemplatePreparseVisitor implements HtmlAstVisitor {
@ -166,3 +238,27 @@ class TemplatePreparseVisitor implements HtmlAstVisitor {
visitExpansionCase(ast: HtmlExpansionCaseAst, context: any): any { return null; } visitExpansionCase(ast: HtmlExpansionCaseAst, 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,
lifecycleHooks: directive.lifecycleHooks,
providers: directive.providers,
viewProviders: directive.viewProviders,
queries: directive.queries,
viewQueries: directive.viewQueries,
precompile: directive.precompile,
template: template
});
}

View File

@ -49,6 +49,16 @@ export class CompileMetadataResolver {
return sanitizeIdentifier(identifier); return sanitizeIdentifier(identifier);
} }
clearCacheFor(compType: Type) {
this._directiveCache.delete(compType);
this._pipeCache.delete(compType);
}
clearCache() {
this._directiveCache.clear();
this._pipeCache.clear();
}
getAnimationEntryMetadata(entry: AnimationEntryMetadata): cpl.CompileAnimationEntryMetadata { getAnimationEntryMetadata(entry: AnimationEntryMetadata): cpl.CompileAnimationEntryMetadata {
var defs = entry.definitions.map(def => this.getAnimationStateMetadata(def)); var defs = entry.definitions.map(def => this.getAnimationStateMetadata(def));
return new cpl.CompileAnimationEntryMetadata(entry.name, defs); return new cpl.CompileAnimationEntryMetadata(entry.name, defs);
@ -101,7 +111,6 @@ export class CompileMetadataResolver {
var moduleUrl = staticTypeModuleUrl(directiveType); var moduleUrl = staticTypeModuleUrl(directiveType);
var precompileTypes: cpl.CompileTypeMetadata[] = []; var precompileTypes: cpl.CompileTypeMetadata[] = [];
if (dirMeta instanceof ComponentMetadata) { if (dirMeta instanceof ComponentMetadata) {
assertArrayOfStrings('styles', dirMeta.styles);
var cmpMeta = <ComponentMetadata>dirMeta; var cmpMeta = <ComponentMetadata>dirMeta;
var viewMeta = this._viewResolver.resolve(directiveType); var viewMeta = this._viewResolver.resolve(directiveType);
assertArrayOfStrings('styles', viewMeta.styles); assertArrayOfStrings('styles', viewMeta.styles);
@ -109,6 +118,8 @@ export class CompileMetadataResolver {
var animations = isPresent(viewMeta.animations) ? var animations = isPresent(viewMeta.animations) ?
viewMeta.animations.map(e => this.getAnimationEntryMetadata(e)) : viewMeta.animations.map(e => this.getAnimationEntryMetadata(e)) :
null; null;
assertArrayOfStrings('styles', viewMeta.styles);
assertArrayOfStrings('styleUrls', viewMeta.styleUrls);
templateMeta = new cpl.CompileTemplateMetadata({ templateMeta = new cpl.CompileTemplateMetadata({
encapsulation: viewMeta.encapsulation, encapsulation: viewMeta.encapsulation,

View File

@ -14,7 +14,7 @@ import {ListWrapper} from './facade/collection';
import {BaseException} from './facade/exceptions'; import {BaseException} from './facade/exceptions';
import {OutputEmitter} from './output/abstract_emitter'; import {OutputEmitter} from './output/abstract_emitter';
import * as o from './output/output_ast'; import * as o from './output/output_ast';
import {StyleCompiler, StylesCompileResult} from './style_compiler'; import {CompiledStylesheet, StyleCompiler} from './style_compiler';
import {TemplateParser} from './template_parser'; import {TemplateParser} from './template_parser';
import {assetUrl} from './util'; import {assetUrl} from './util';
import {ComponentFactoryDependency, ViewCompileResult, ViewCompiler, ViewFactoryDependency} from './view_compiler/view_compiler'; import {ComponentFactoryDependency, ViewCompileResult, ViewCompiler, ViewFactoryDependency} from './view_compiler/view_compiler';
@ -44,29 +44,38 @@ export class OfflineCompiler {
constructor( constructor(
private _directiveNormalizer: DirectiveNormalizer, private _templateParser: TemplateParser, private _directiveNormalizer: DirectiveNormalizer, private _templateParser: TemplateParser,
private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler, private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler,
private _outputEmitter: OutputEmitter, private _xhr: XHR) {} private _outputEmitter: OutputEmitter) {}
normalizeDirectiveMetadata(directive: CompileDirectiveMetadata): normalizeDirectiveMetadata(directive: CompileDirectiveMetadata):
Promise<CompileDirectiveMetadata> { Promise<CompileDirectiveMetadata> {
return this._directiveNormalizer.normalizeDirective(directive); return this._directiveNormalizer.normalizeDirective(directive).asyncResult;
} }
compileTemplates(components: NormalizedComponentWithViewDirectives[]): SourceModule { compileTemplates(components: NormalizedComponentWithViewDirectives[]): SourceModule[] {
if (components.length === 0) { if (components.length === 0) {
throw new BaseException('No components given'); throw new BaseException('No components given');
} }
var statements: o.DeclareVarStmt[] = []; var statements: o.DeclareVarStmt[] = [];
var exportedVars: string[] = []; var exportedVars: string[] = [];
var moduleUrl = _ngfactoryModuleUrl(components[0].component.type); var moduleUrl = _ngfactoryModuleUrl(components[0].component.type);
var outputSourceModules: SourceModule[] = [];
components.forEach(componentWithDirs => { components.forEach(componentWithDirs => {
var compMeta = <CompileDirectiveMetadata>componentWithDirs.component; var compMeta = <CompileDirectiveMetadata>componentWithDirs.component;
_assertComponent(compMeta); _assertComponent(compMeta);
var fileSuffix = _splitLastSuffix(compMeta.type.moduleUrl)[1];
var stylesCompileResults = this._styleCompiler.compileComponent(compMeta);
stylesCompileResults.externalStylesheets.forEach((compiledStyleSheet) => {
outputSourceModules.push(this._codgenStyles(compiledStyleSheet, fileSuffix));
});
var compViewFactoryVar = this._compileComponent( var compViewFactoryVar = this._compileComponent(
compMeta, componentWithDirs.directives, componentWithDirs.pipes, statements); compMeta, componentWithDirs.directives, componentWithDirs.pipes,
stylesCompileResults.componentStylesheet, fileSuffix, statements);
exportedVars.push(compViewFactoryVar); exportedVars.push(compViewFactoryVar);
var hostMeta = createHostComponentMeta(compMeta.type, compMeta.selector); var hostMeta = createHostComponentMeta(compMeta.type, compMeta.selector);
var hostViewFactoryVar = this._compileComponent(hostMeta, [compMeta], [], statements); var hostViewFactoryVar =
this._compileComponent(hostMeta, [compMeta], [], null, fileSuffix, statements);
var compFactoryVar = _componentFactoryName(compMeta.type); var compFactoryVar = _componentFactoryName(compMeta.type);
statements.push( statements.push(
o.variable(compFactoryVar) o.variable(compFactoryVar)
@ -82,43 +91,32 @@ export class OfflineCompiler {
.toDeclStmt(null, [o.StmtModifier.Final])); .toDeclStmt(null, [o.StmtModifier.Final]));
exportedVars.push(compFactoryVar); exportedVars.push(compFactoryVar);
}); });
return this._codegenSourceModule(moduleUrl, statements, exportedVars); outputSourceModules.unshift(this._codegenSourceModule(moduleUrl, statements, exportedVars));
} return outputSourceModules;
loadAndCompileStylesheet(stylesheetUrl: string, shim: boolean, suffix: string):
Promise<StyleSheetSourceWithImports> {
return this._xhr.get(stylesheetUrl).then((cssText) => {
var compileResult = this._styleCompiler.compileStylesheet(stylesheetUrl, cssText, shim);
var importedUrls: string[] = [];
compileResult.dependencies.forEach((dep) => {
importedUrls.push(dep.moduleUrl);
dep.valuePlaceholder.moduleUrl = _stylesModuleUrl(dep.moduleUrl, dep.isShimmed, suffix);
});
return new StyleSheetSourceWithImports(
this._codgenStyles(stylesheetUrl, shim, suffix, compileResult), importedUrls);
});
} }
private _compileComponent( private _compileComponent(
compMeta: CompileDirectiveMetadata, directives: CompileDirectiveMetadata[], compMeta: CompileDirectiveMetadata, directives: CompileDirectiveMetadata[],
pipes: CompilePipeMetadata[], targetStatements: o.Statement[]): string { pipes: CompilePipeMetadata[], componentStyles: CompiledStylesheet, fileSuffix: string,
var styleResult = this._styleCompiler.compileComponent(compMeta); targetStatements: o.Statement[]): string {
var parsedTemplate = this._templateParser.parse( var parsedTemplate = this._templateParser.parse(
compMeta, compMeta.template.template, directives, pipes, compMeta.type.name); compMeta, compMeta.template.template, directives, pipes, compMeta.type.name);
var viewResult = this._viewCompiler.compileComponent( var stylesExpr = componentStyles ? o.variable(componentStyles.stylesVar) : o.literalArr([]);
compMeta, parsedTemplate, o.variable(styleResult.stylesVar), pipes); var viewResult =
ListWrapper.addAll( this._viewCompiler.compileComponent(compMeta, parsedTemplate, stylesExpr, pipes);
targetStatements, _resolveStyleStatements(compMeta.type.moduleUrl, styleResult)); if (componentStyles) {
ListWrapper.addAll(targetStatements, _resolveStyleStatements(componentStyles, fileSuffix));
}
ListWrapper.addAll(targetStatements, _resolveViewStatements(viewResult)); ListWrapper.addAll(targetStatements, _resolveViewStatements(viewResult));
return viewResult.viewFactoryVar; return viewResult.viewFactoryVar;
} }
private _codgenStyles( private _codgenStyles(stylesCompileResult: CompiledStylesheet, fileSuffix: string): SourceModule {
inputUrl: string, shim: boolean, suffix: string, _resolveStyleStatements(stylesCompileResult, fileSuffix);
stylesCompileResult: StylesCompileResult): SourceModule {
return this._codegenSourceModule( return this._codegenSourceModule(
_stylesModuleUrl(inputUrl, shim, suffix), stylesCompileResult.statements, _stylesModuleUrl(
[stylesCompileResult.stylesVar]); stylesCompileResult.meta.moduleUrl, stylesCompileResult.isShimmed, fileSuffix),
stylesCompileResult.statements, [stylesCompileResult.stylesVar]);
} }
private _codegenSourceModule( private _codegenSourceModule(
@ -131,7 +129,7 @@ export class OfflineCompiler {
function _resolveViewStatements(compileResult: ViewCompileResult): o.Statement[] { function _resolveViewStatements(compileResult: ViewCompileResult): o.Statement[] {
compileResult.dependencies.forEach((dep) => { compileResult.dependencies.forEach((dep) => {
if (dep instanceof ViewFactoryDependency) { if (dep instanceof ViewFactoryDependency) {
dep.placeholder.moduleUrl = _ngfactoryModuleUrl(dep.comp.type); dep.placeholder.moduleUrl = _ngfactoryModuleUrl(dep.comp);
} else if (dep instanceof ComponentFactoryDependency) { } else if (dep instanceof ComponentFactoryDependency) {
dep.placeholder.name = _componentFactoryName(dep.comp); dep.placeholder.name = _componentFactoryName(dep.comp);
dep.placeholder.moduleUrl = _ngfactoryModuleUrl(dep.comp); dep.placeholder.moduleUrl = _ngfactoryModuleUrl(dep.comp);
@ -142,17 +140,15 @@ function _resolveViewStatements(compileResult: ViewCompileResult): o.Statement[]
function _resolveStyleStatements( function _resolveStyleStatements(
containingModuleUrl: string, compileResult: StylesCompileResult): o.Statement[] { compileResult: CompiledStylesheet, fileSuffix: string): o.Statement[] {
var containingSuffix = _splitSuffix(containingModuleUrl)[1];
compileResult.dependencies.forEach((dep) => { compileResult.dependencies.forEach((dep) => {
dep.valuePlaceholder.moduleUrl = dep.valuePlaceholder.moduleUrl = _stylesModuleUrl(dep.moduleUrl, dep.isShimmed, fileSuffix);
_stylesModuleUrl(dep.moduleUrl, dep.isShimmed, containingSuffix);
}); });
return compileResult.statements; return compileResult.statements;
} }
function _ngfactoryModuleUrl(comp: CompileIdentifierMetadata): string { function _ngfactoryModuleUrl(comp: CompileIdentifierMetadata): string {
var urlWithSuffix = _splitSuffix(comp.moduleUrl); var urlWithSuffix = _splitLastSuffix(comp.moduleUrl);
return `${urlWithSuffix[0]}.ngfactory${urlWithSuffix[1]}`; return `${urlWithSuffix[0]}.ngfactory${urlWithSuffix[1]}`;
} }
@ -170,7 +166,7 @@ function _assertComponent(meta: CompileDirectiveMetadata) {
} }
} }
function _splitSuffix(path: string): string[] { function _splitLastSuffix(path: string): string[] {
let lastDot = path.lastIndexOf('.'); let lastDot = path.lastIndexOf('.');
if (lastDot !== -1) { if (lastDot !== -1) {
return [path.substring(0, lastDot), path.substring(lastDot)]; return [path.substring(0, lastDot), path.substring(lastDot)];

View File

@ -6,26 +6,25 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ComponentFactory, ComponentResolver, Injectable} from '@angular/core'; import {Compiler, ComponentFactory, ComponentResolver, Injectable} from '@angular/core';
import {BaseException} from '../src/facade/exceptions'; import {BaseException} from '../src/facade/exceptions';
import {IS_DART, Type, isBlank, isString} from '../src/facade/lang'; import {ConcreteType, IS_DART, Type, isBlank, isString, stringify} from '../src/facade/lang';
import {ListWrapper,} from '../src/facade/collection'; import {ListWrapper,} from '../src/facade/collection';
import {PromiseWrapper} from '../src/facade/async'; import {PromiseWrapper} from '../src/facade/async';
import {createHostComponentMeta, CompileDirectiveMetadata, CompilePipeMetadata, CompileIdentifierMetadata} from './compile_metadata'; import {createHostComponentMeta, CompileDirectiveMetadata, CompilePipeMetadata, CompileIdentifierMetadata} from './compile_metadata';
import {TemplateAst,} from './template_ast'; import {TemplateAst,} from './template_ast';
import {StyleCompiler, StylesCompileDependency, StylesCompileResult} from './style_compiler'; import {StyleCompiler, StylesCompileDependency, CompiledStylesheet} from './style_compiler';
import {ViewCompiler, ViewFactoryDependency, ComponentFactoryDependency} from './view_compiler/view_compiler'; import {ViewCompiler, ViewCompileResult, ViewFactoryDependency, ComponentFactoryDependency} from './view_compiler/view_compiler';
import {TemplateParser} from './template_parser'; import {TemplateParser} from './template_parser';
import {DirectiveNormalizer} from './directive_normalizer'; import {DirectiveNormalizer, NormalizeDirectiveResult} from './directive_normalizer';
import {CompileMetadataResolver} from './metadata_resolver'; import {CompileMetadataResolver} from './metadata_resolver';
import {CompilerConfig} from './config'; import {CompilerConfig} from './config';
import * as ir from './output/output_ast'; import * as ir from './output/output_ast';
import {jitStatements} from './output/output_jit'; import {jitStatements} from './output/output_jit';
import {interpretStatements} from './output/output_interpreter'; import {interpretStatements} from './output/output_interpreter';
import {InterpretiveAppViewInstanceFactory} from './output/interpretive_view'; import {InterpretiveAppViewInstanceFactory} from './output/interpretive_view';
import {XHR} from './xhr';
/** /**
* An internal module of the Angular compiler that begins with component types, * An internal module of the Angular compiler that begins with component types,
@ -33,15 +32,14 @@ import {XHR} from './xhr';
* ready for linking into an application. * ready for linking into an application.
*/ */
@Injectable() @Injectable()
export class RuntimeCompiler implements ComponentResolver { export class RuntimeCompiler implements ComponentResolver, Compiler {
private _styleCache: Map<string, Promise<string>> = new Map<string, Promise<string>>();
private _hostCacheKeys = new Map<Type, any>();
private _compiledTemplateCache = new Map<any, CompiledTemplate>(); private _compiledTemplateCache = new Map<any, CompiledTemplate>();
private _compiledHostTemplateCache = new Map<any, CompiledTemplate>();
constructor( constructor(
private _metadataResolver: CompileMetadataResolver, private _metadataResolver: CompileMetadataResolver,
private _templateNormalizer: DirectiveNormalizer, private _templateParser: TemplateParser, private _templateNormalizer: DirectiveNormalizer, private _templateParser: TemplateParser,
private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler, private _xhr: XHR, private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler,
private _genConfig: CompilerConfig) {} private _genConfig: CompilerConfig) {}
resolveComponent(component: Type|string): Promise<ComponentFactory<any>> { resolveComponent(component: Type|string): Promise<ComponentFactory<any>> {
@ -49,175 +47,215 @@ export class RuntimeCompiler implements ComponentResolver {
return PromiseWrapper.reject( return PromiseWrapper.reject(
new BaseException(`Cannot resolve component using '${component}'.`), null); new BaseException(`Cannot resolve component using '${component}'.`), null);
} }
return this._loadAndCompileHostComponent(<Type>component).done; return this.compileComponentAsync(<ConcreteType<any>>component);
}
compileComponentAsync<T>(compType: ConcreteType<T>): Promise<ComponentFactory<T>> {
var templates = this._getTransitiveCompiledTemplates(compType, true);
var loadingPromises: Promise<any>[] = [];
templates.forEach((template) => {
if (template.loading) {
loadingPromises.push(template.loading);
}
});
return Promise.all(loadingPromises).then(() => {
templates.forEach((template) => { this._compileTemplate(template); });
return this._getCompiledHostTemplate(compType).proxyComponentFactory;
});
}
compileComponentSync<T>(compType: ConcreteType<T>): ComponentFactory<T> {
var templates = this._getTransitiveCompiledTemplates(compType, true);
templates.forEach((template) => {
if (template.loading) {
throw new BaseException(
`Can't compile synchronously as ${template.compType.name} is still being loaded!`);
}
});
templates.forEach((template) => { this._compileTemplate(template); });
return this._getCompiledHostTemplate(compType).proxyComponentFactory;
}
clearCacheFor(compType: Type) {
this._metadataResolver.clearCacheFor(compType);
this._compiledHostTemplateCache.delete(compType);
var compiledTemplate = this._compiledTemplateCache.get(compType);
if (compiledTemplate) {
this._templateNormalizer.clearCacheFor(compiledTemplate.normalizedCompMeta);
this._compiledTemplateCache.delete(compType);
}
} }
clearCache(): void { clearCache(): void {
this._styleCache.clear(); this._metadataResolver.clearCache();
this._compiledTemplateCache.clear(); this._compiledTemplateCache.clear();
this._hostCacheKeys.clear(); this._compiledHostTemplateCache.clear();
this._templateNormalizer.clearCache();
} }
private _loadAndCompileHostComponent(componentType: Type): CompileHostTemplate { private _getCompiledHostTemplate(type: Type): CompiledTemplate {
var compMeta: CompileDirectiveMetadata = var compiledTemplate = this._compiledHostTemplateCache.get(type);
this._metadataResolver.getDirectiveMetadata(componentType);
var hostCacheKey = this._hostCacheKeys.get(compMeta.type.runtime);
if (isBlank(hostCacheKey)) {
hostCacheKey = new Object();
this._hostCacheKeys.set(compMeta.type.runtime, hostCacheKey);
assertComponent(compMeta);
var hostMeta: CompileDirectiveMetadata =
createHostComponentMeta(compMeta.type, compMeta.selector);
this._loadAndCompileComponent(hostCacheKey, hostMeta, [compMeta], [], []);
}
var compTemplate = this._compiledTemplateCache.get(hostCacheKey);
return new CompileHostTemplate(compTemplate, compMeta);
}
private _loadAndCompileComponent(
cacheKey: any, compMeta: CompileDirectiveMetadata, viewDirectives: CompileDirectiveMetadata[],
pipes: CompilePipeMetadata[], compilingComponentsPath: any[]): CompiledTemplate {
var compiledTemplate = this._compiledTemplateCache.get(cacheKey);
if (isBlank(compiledTemplate)) { if (isBlank(compiledTemplate)) {
let done = var compMeta = this._metadataResolver.getDirectiveMetadata(type);
PromiseWrapper assertComponent(compMeta);
.all([<any>this._compileComponentStyles(compMeta)].concat(viewDirectives.map( var hostMeta = createHostComponentMeta(compMeta.type, compMeta.selector);
dirMeta => this._templateNormalizer.normalizeDirective(dirMeta)))) compiledTemplate = new CompiledTemplate(
.then((stylesAndNormalizedViewDirMetas: any[]) => { true, compMeta.selector, compMeta.type, [], [type], [], [],
var normalizedViewDirMetas = stylesAndNormalizedViewDirMetas.slice(1); this._templateNormalizer.normalizeDirective(hostMeta));
var styles = stylesAndNormalizedViewDirMetas[0]; this._compiledHostTemplateCache.set(type, compiledTemplate);
var parsedTemplate = this._templateParser.parse(
compMeta, compMeta.template.template, normalizedViewDirMetas, pipes,
compMeta.type.name);
var childPromises: Promise<any>[] = [];
compiledTemplate.init(this._compileComponent(
compMeta, parsedTemplate, styles, pipes, compilingComponentsPath,
childPromises));
return PromiseWrapper.all(childPromises).then((_) => { return compiledTemplate; });
});
compiledTemplate = new CompiledTemplate(done);
this._compiledTemplateCache.set(cacheKey, compiledTemplate);
} }
return compiledTemplate; return compiledTemplate;
} }
private _compileComponent( private _getCompiledTemplate(type: Type): CompiledTemplate {
compMeta: CompileDirectiveMetadata, parsedTemplate: TemplateAst[], styles: string[], var compiledTemplate = this._compiledTemplateCache.get(type);
pipes: CompilePipeMetadata[], compilingComponentsPath: any[], if (isBlank(compiledTemplate)) {
childPromises: Promise<any>[]): Function { var compMeta = this._metadataResolver.getDirectiveMetadata(type);
var compileResult = this._viewCompiler.compileComponent( assertComponent(compMeta);
compMeta, parsedTemplate, var viewDirectives: CompileDirectiveMetadata[] = [];
new ir.ExternalExpr(new CompileIdentifierMetadata({runtime: styles})), pipes); var viewComponentTypes: Type[] = [];
compileResult.dependencies.forEach((dep) => { this._metadataResolver.getViewDirectivesMetadata(type).forEach(dirOrComp => {
if (dep instanceof ViewFactoryDependency) { if (dirOrComp.isComponent) {
let childCompilingComponentsPath = ListWrapper.clone(compilingComponentsPath); viewComponentTypes.push(dirOrComp.type.runtime);
let childCacheKey = dep.comp.type.runtime; } else {
let childViewDirectives: CompileDirectiveMetadata[] = viewDirectives.push(dirOrComp);
this._metadataResolver.getViewDirectivesMetadata(dep.comp.type.runtime);
let childViewPipes: CompilePipeMetadata[] =
this._metadataResolver.getViewPipesMetadata(dep.comp.type.runtime);
let childIsRecursive = childCompilingComponentsPath.indexOf(childCacheKey) > -1;
childCompilingComponentsPath.push(childCacheKey);
let childComp = this._loadAndCompileComponent(
dep.comp.type.runtime, dep.comp, childViewDirectives, childViewPipes,
childCompilingComponentsPath);
dep.placeholder.runtime = childComp.proxyViewFactory;
dep.placeholder.name = `viewFactory_${dep.comp.type.name}`;
if (!childIsRecursive) {
// Only wait for a child if it is not a cycle
childPromises.push(childComp.done);
} }
});
var precompileComponentTypes = compMeta.precompile.map((typeMeta) => typeMeta.runtime);
var pipes = this._metadataResolver.getViewPipesMetadata(type);
compiledTemplate = new CompiledTemplate(
false, compMeta.selector, compMeta.type, viewDirectives, viewComponentTypes,
precompileComponentTypes, pipes, this._templateNormalizer.normalizeDirective(compMeta));
this._compiledTemplateCache.set(type, compiledTemplate);
}
return compiledTemplate;
}
private _getTransitiveCompiledTemplates(
compType: Type, isHost: boolean,
target: Set<CompiledTemplate> = new Set<CompiledTemplate>()): Set<CompiledTemplate> {
var template =
isHost ? this._getCompiledHostTemplate(compType) : this._getCompiledTemplate(compType);
if (!target.has(template)) {
target.add(template);
template.viewComponentTypes.forEach(
(compType) => { this._getTransitiveCompiledTemplates(compType, false, target); });
template.precompileHostComponentTypes.forEach(
(compType) => { this._getTransitiveCompiledTemplates(compType, true, target); });
}
return target;
}
private _compileTemplate(template: CompiledTemplate) {
if (template.isCompiled) {
return;
}
var compMeta = template.normalizedCompMeta;
var externalStylesheetsByModuleUrl = new Map<string, CompiledStylesheet>();
var stylesCompileResult = this._styleCompiler.compileComponent(compMeta);
stylesCompileResult.externalStylesheets.forEach(
(r) => { externalStylesheetsByModuleUrl.set(r.meta.moduleUrl, r); });
this._resolveStylesCompileResult(
stylesCompileResult.componentStylesheet, externalStylesheetsByModuleUrl);
var viewCompMetas = template.viewComponentTypes.map(
(compType) => this._getCompiledTemplate(compType).normalizedCompMeta);
var parsedTemplate = this._templateParser.parse(
compMeta, compMeta.template.template, template.viewDirectives.concat(viewCompMetas),
template.viewPipes, compMeta.type.name);
var compileResult = this._viewCompiler.compileComponent(
compMeta, parsedTemplate, ir.variable(stylesCompileResult.componentStylesheet.stylesVar),
template.viewPipes);
var depTemplates = compileResult.dependencies.map((dep) => {
let depTemplate: CompiledTemplate;
if (dep instanceof ViewFactoryDependency) {
depTemplate = this._getCompiledTemplate(dep.comp.runtime);
dep.placeholder.runtime = depTemplate.proxyViewFactory;
dep.placeholder.name = `viewFactory_${dep.comp.name}`;
} else if (dep instanceof ComponentFactoryDependency) { } else if (dep instanceof ComponentFactoryDependency) {
let childComp = this._loadAndCompileHostComponent(dep.comp.runtime); depTemplate = this._getCompiledHostTemplate(dep.comp.runtime);
dep.placeholder.runtime = childComp.componentFactory; dep.placeholder.runtime = depTemplate.proxyComponentFactory;
dep.placeholder.name = `compFactory_${dep.comp.name}`; dep.placeholder.name = `compFactory_${dep.comp.name}`;
childPromises.push(childComp.done);
} }
return depTemplate;
}); });
var statements =
stylesCompileResult.componentStylesheet.statements.concat(compileResult.statements);
var factory: any; var factory: any;
if (IS_DART || !this._genConfig.useJit) { if (IS_DART || !this._genConfig.useJit) {
factory = interpretStatements( factory = interpretStatements(
compileResult.statements, compileResult.viewFactoryVar, statements, compileResult.viewFactoryVar, new InterpretiveAppViewInstanceFactory());
new InterpretiveAppViewInstanceFactory());
} else { } else {
factory = jitStatements( factory = jitStatements(
`${compMeta.type.name}.template.js`, compileResult.statements, `${template.compType.name}.template.js`, statements, compileResult.viewFactoryVar);
compileResult.viewFactoryVar);
} }
return factory; template.compiled(factory);
} }
private _compileComponentStyles(compMeta: CompileDirectiveMetadata): Promise<string[]> { private _resolveStylesCompileResult(
var compileResult = this._styleCompiler.compileComponent(compMeta); result: CompiledStylesheet, externalStylesheetsByModuleUrl: Map<string, CompiledStylesheet>) {
return this._resolveStylesCompileResult(compMeta.type.name, compileResult); result.dependencies.forEach((dep, i) => {
var nestedCompileResult = externalStylesheetsByModuleUrl.get(dep.moduleUrl);
var nestedStylesArr = this._resolveAndEvalStylesCompileResult(
nestedCompileResult, externalStylesheetsByModuleUrl);
dep.valuePlaceholder.runtime = nestedStylesArr;
dep.valuePlaceholder.name = `importedStyles${i}`;
});
} }
private _resolveStylesCompileResult(sourceUrl: string, result: StylesCompileResult): private _resolveAndEvalStylesCompileResult(
Promise<string[]> { result: CompiledStylesheet,
var promises = result.dependencies.map((dep) => this._loadStylesheetDep(dep)); externalStylesheetsByModuleUrl: Map<string, CompiledStylesheet>): string[] {
return PromiseWrapper.all(promises) this._resolveStylesCompileResult(result, externalStylesheetsByModuleUrl);
.then((cssTexts) => { if (IS_DART || !this._genConfig.useJit) {
var nestedCompileResultPromises: Promise<string[]>[] = []; return interpretStatements(
for (var i = 0; i < result.dependencies.length; i++) { result.statements, result.stylesVar, new InterpretiveAppViewInstanceFactory());
var dep = result.dependencies[i]; } else {
var cssText = cssTexts[i]; return jitStatements(`${result.meta.moduleUrl}.css.js`, result.statements, result.stylesVar);
var nestedCompileResult =
this._styleCompiler.compileStylesheet(dep.moduleUrl, cssText, dep.isShimmed);
nestedCompileResultPromises.push(
this._resolveStylesCompileResult(dep.moduleUrl, nestedCompileResult));
}
return PromiseWrapper.all(nestedCompileResultPromises);
})
.then((nestedStylesArr) => {
for (var i = 0; i < result.dependencies.length; i++) {
var dep = result.dependencies[i];
dep.valuePlaceholder.runtime = nestedStylesArr[i];
dep.valuePlaceholder.name = `importedStyles${i}`;
}
if (IS_DART || !this._genConfig.useJit) {
return interpretStatements(
result.statements, result.stylesVar, new InterpretiveAppViewInstanceFactory());
} else {
return jitStatements(`${sourceUrl}.css.js`, result.statements, result.stylesVar);
}
});
}
private _loadStylesheetDep(dep: StylesCompileDependency): Promise<string> {
var cacheKey = `${dep.moduleUrl}${dep.isShimmed ? '.shim' : ''}`;
var cssTextPromise = this._styleCache.get(cacheKey);
if (isBlank(cssTextPromise)) {
cssTextPromise = this._xhr.get(dep.moduleUrl);
this._styleCache.set(cacheKey, cssTextPromise);
} }
return cssTextPromise;
}
}
class CompileHostTemplate {
componentFactory: ComponentFactory<any>;
done: Promise<ComponentFactory<any>>;
constructor(_template: CompiledTemplate, compMeta: CompileDirectiveMetadata) {
this.componentFactory = new ComponentFactory<any>(
compMeta.selector, _template.proxyViewFactory, compMeta.type.runtime);
this.done = _template.done.then((_) => this.componentFactory);
} }
} }
class CompiledTemplate { class CompiledTemplate {
private _viewFactory: Function = null; private _viewFactory: Function = null;
proxyViewFactory: Function; proxyViewFactory: Function;
constructor(public done: Promise<CompiledTemplate>) { proxyComponentFactory: ComponentFactory<any>;
this.proxyViewFactory = loading: Promise<any> = null;
(viewUtils: any /** TODO #9100 */, childInjector: any /** TODO #9100 */, private _normalizedCompMeta: CompileDirectiveMetadata = null;
contextEl: any /** TODO #9100 */) => isCompiled = false;
this._viewFactory(viewUtils, childInjector, contextEl); isCompiledWithDeps = false;
constructor(
public isHost: boolean, selector: string, public compType: CompileIdentifierMetadata,
public viewDirectives: CompileDirectiveMetadata[], public viewComponentTypes: Type[],
public precompileHostComponentTypes: Type[], public viewPipes: CompilePipeMetadata[],
private _normalizeResult: NormalizeDirectiveResult) {
this.proxyViewFactory = (...args: any[]) => this._viewFactory.apply(null, args);
this.proxyComponentFactory = isHost ?
new ComponentFactory<any>(selector, this.proxyViewFactory, compType.runtime) :
null;
if (_normalizeResult.syncResult) {
this._normalizedCompMeta = _normalizeResult.syncResult;
} else {
this.loading = _normalizeResult.asyncResult.then((normalizedCompMeta) => {
this._normalizedCompMeta = normalizedCompMeta;
this.loading = null;
});
}
} }
init(viewFactory: Function) { this._viewFactory = viewFactory; } get normalizedCompMeta(): CompileDirectiveMetadata {
if (this.loading) {
throw new BaseException(`Template is still loading for ${this.compType.name}!`);
}
return this._normalizedCompMeta;
}
compiled(viewFactory: Function) {
this._viewFactory = viewFactory;
this.isCompiled = true;
}
depsCompiled() { this.isCompiledWithDeps = true; }
} }
function assertComponent(meta: CompileDirectiveMetadata) { function assertComponent(meta: CompileDirectiveMetadata) {

View File

@ -10,7 +10,7 @@ import {Injectable, ViewEncapsulation} from '@angular/core';
import {isPresent} from '../src/facade/lang'; import {isPresent} from '../src/facade/lang';
import {CompileDirectiveMetadata, CompileIdentifierMetadata} from './compile_metadata'; import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileStylesheetMetadata} from './compile_metadata';
import * as o from './output/output_ast'; import * as o from './output/output_ast';
import {ShadowCss} from './shadow_css'; import {ShadowCss} from './shadow_css';
import {extractStyleUrls} from './style_url_resolver'; import {extractStyleUrls} from './style_url_resolver';
@ -27,9 +27,16 @@ export class StylesCompileDependency {
} }
export class StylesCompileResult { export class StylesCompileResult {
constructor(
public componentStylesheet: CompiledStylesheet,
public externalStylesheets: CompiledStylesheet[]) {}
}
export class CompiledStylesheet {
constructor( constructor(
public statements: o.Statement[], public stylesVar: string, public statements: o.Statement[], public stylesVar: string,
public dependencies: StylesCompileDependency[]) {} public dependencies: StylesCompileDependency[], public isShimmed: boolean,
public meta: CompileStylesheetMetadata) {}
} }
@Injectable() @Injectable()
@ -40,35 +47,41 @@ export class StyleCompiler {
compileComponent(comp: CompileDirectiveMetadata): StylesCompileResult { compileComponent(comp: CompileDirectiveMetadata): StylesCompileResult {
var shim = comp.template.encapsulation === ViewEncapsulation.Emulated; var shim = comp.template.encapsulation === ViewEncapsulation.Emulated;
return this._compileStyles( var externalStylesheets: CompiledStylesheet[] = [];
getStylesVarName(comp), comp.template.styles, comp.template.styleUrls, shim); var componentStylesheet: CompiledStylesheet = this._compileStyles(
} comp, new CompileStylesheetMetadata({
styles: comp.template.styles,
compileStylesheet(stylesheetUrl: string, cssText: string, isShimmed: boolean): styleUrls: comp.template.styleUrls,
StylesCompileResult { moduleUrl: comp.type.moduleUrl
var styleWithImports = extractStyleUrls(this._urlResolver, stylesheetUrl, cssText); }),
return this._compileStyles( true);
getStylesVarName(null), [styleWithImports.style], styleWithImports.styleUrls, isShimmed); comp.template.externalStylesheets.forEach((stylesheetMeta) => {
var compiledStylesheet = this._compileStyles(comp, stylesheetMeta, false);
externalStylesheets.push(compiledStylesheet);
});
return new StylesCompileResult(componentStylesheet, externalStylesheets);
} }
private _compileStyles( private _compileStyles(
stylesVar: string, plainStyles: string[], absUrls: string[], comp: CompileDirectiveMetadata, stylesheet: CompileStylesheetMetadata,
shim: boolean): StylesCompileResult { isComponentStylesheet: boolean): CompiledStylesheet {
var shim = comp.template.encapsulation === ViewEncapsulation.Emulated;
var styleExpressions = var styleExpressions =
plainStyles.map(plainStyle => o.literal(this._shimIfNeeded(plainStyle, shim))); stylesheet.styles.map(plainStyle => o.literal(this._shimIfNeeded(plainStyle, shim)));
var dependencies: StylesCompileDependency[] = []; var dependencies: StylesCompileDependency[] = [];
for (var i = 0; i < absUrls.length; i++) { for (var i = 0; i < stylesheet.styleUrls.length; i++) {
var identifier = new CompileIdentifierMetadata({name: getStylesVarName(null)}); var identifier = new CompileIdentifierMetadata({name: getStylesVarName(null)});
dependencies.push(new StylesCompileDependency(absUrls[i], shim, identifier)); dependencies.push(new StylesCompileDependency(stylesheet.styleUrls[i], shim, identifier));
styleExpressions.push(new o.ExternalExpr(identifier)); styleExpressions.push(new o.ExternalExpr(identifier));
} }
// styles variable contains plain strings and arrays of other styles arrays (recursive), // styles variable contains plain strings and arrays of other styles arrays (recursive),
// so we set its type to dynamic. // so we set its type to dynamic.
var stylesVar = getStylesVarName(isComponentStylesheet ? comp : null);
var stmt = o.variable(stylesVar) var stmt = o.variable(stylesVar)
.set(o.literalArr( .set(o.literalArr(
styleExpressions, new o.ArrayType(o.DYNAMIC_TYPE, [o.TypeModifier.Const]))) styleExpressions, new o.ArrayType(o.DYNAMIC_TYPE, [o.TypeModifier.Const])))
.toDeclStmt(null, [o.StmtModifier.Final]); .toDeclStmt(null, [o.StmtModifier.Final]);
return new StylesCompileResult([stmt], stylesVar, dependencies); return new CompiledStylesheet([stmt], stylesVar, dependencies, shim, stylesheet);
} }
private _shimIfNeeded(style: string, shim: boolean): string { private _shimIfNeeded(style: string, shim: boolean): string {
@ -78,7 +91,7 @@ export class StyleCompiler {
function getStylesVarName(component: CompileDirectiveMetadata): string { function getStylesVarName(component: CompileDirectiveMetadata): string {
var result = `styles`; var result = `styles`;
if (isPresent(component)) { if (component) {
result += `_${component.type.name}`; result += `_${component.type.name}`;
} }
return result; return result;

View File

@ -32,7 +32,7 @@ var rootSelectorVar = o.variable('rootSelector');
export class ViewFactoryDependency { export class ViewFactoryDependency {
constructor( constructor(
public comp: CompileDirectiveMetadata, public placeholder: CompileIdentifierMetadata) {} public comp: CompileIdentifierMetadata, public placeholder: CompileIdentifierMetadata) {}
} }
export class ComponentFactoryDependency { export class ComponentFactoryDependency {
@ -208,7 +208,8 @@ class ViewBuilderVisitor implements TemplateAstVisitor {
if (isPresent(component)) { if (isPresent(component)) {
let nestedComponentIdentifier = let nestedComponentIdentifier =
new CompileIdentifierMetadata({name: getViewFactoryName(component, 0)}); new CompileIdentifierMetadata({name: getViewFactoryName(component, 0)});
this.targetDependencies.push(new ViewFactoryDependency(component, nestedComponentIdentifier)); this.targetDependencies.push(
new ViewFactoryDependency(component.type, nestedComponentIdentifier));
let precompileComponentIdentifiers = let precompileComponentIdentifiers =
component.precompile.map((precompileComp: CompileIdentifierMetadata) => { component.precompile.map((precompileComp: CompileIdentifierMetadata) => {
var id = new CompileIdentifierMetadata({name: precompileComp.name}); var id = new CompileIdentifierMetadata({name: precompileComp.name});

View File

@ -19,24 +19,9 @@ import {Map} from '../src/facade/collection';
*/ */
@Injectable() @Injectable()
export class ViewResolver { export class ViewResolver {
/** @internal */
_cache = new Map<Type, ViewMetadata>();
constructor(private _reflector: ReflectorReader = reflector) {} constructor(private _reflector: ReflectorReader = reflector) {}
resolve(component: Type): ViewMetadata { resolve(component: Type): ViewMetadata {
var view = this._cache.get(component);
if (isBlank(view)) {
view = this._resolve(component);
this._cache.set(component, view);
}
return view;
}
/** @internal */
_resolve(component: Type): ViewMetadata {
var compMeta: ComponentMetadata; var compMeta: ComponentMetadata;
this._reflector.annotations(component).forEach(m => { this._reflector.annotations(component).forEach(m => {

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {CompileTemplateMetadata, CompileTypeMetadata} from '@angular/compiler/src/compile_metadata'; import {CompileDirectiveMetadata, CompileStylesheetMetadata, CompileTemplateMetadata, CompileTypeMetadata} from '@angular/compiler/src/compile_metadata';
import {CompilerConfig} from '@angular/compiler/src/config'; import {CompilerConfig} from '@angular/compiler/src/config';
import {DirectiveNormalizer} from '@angular/compiler/src/directive_normalizer'; import {DirectiveNormalizer} from '@angular/compiler/src/directive_normalizer';
import {XHR} from '@angular/compiler/src/xhr'; import {XHR} from '@angular/compiler/src/xhr';
@ -15,6 +15,7 @@ import {ViewEncapsulation} from '@angular/core/src/metadata/view';
import {beforeEach, beforeEachProviders, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal'; import {beforeEach, beforeEachProviders, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
import {AsyncTestCompleter} from '@angular/core/testing/testing_internal'; import {AsyncTestCompleter} from '@angular/core/testing/testing_internal';
import {SpyXHR} from './spies';
import {TEST_PROVIDERS} from './test_bindings'; import {TEST_PROVIDERS} from './test_bindings';
export function main() { export function main() {
@ -30,176 +31,230 @@ export function main() {
new CompileTypeMetadata({moduleUrl: 'http://some/module/a.js', name: 'SomeComp'}); new CompileTypeMetadata({moduleUrl: 'http://some/module/a.js', name: 'SomeComp'});
}); });
describe('loadTemplate', () => { describe('normalizeDirective', () => {
describe('inline template', () => {
it('should store the template',
inject(
[AsyncTestCompleter, DirectiveNormalizer],
(async: AsyncTestCompleter, normalizer: DirectiveNormalizer) => {
normalizer
.normalizeTemplate(dirType, new CompileTemplateMetadata({
encapsulation: null,
template: 'a',
templateUrl: null,
styles: [],
styleUrls: ['test.css']
}))
.then((template: CompileTemplateMetadata) => {
expect(template.template).toEqual('a');
expect(template.templateUrl).toEqual('package:some/module/a.js');
async.done();
});
}));
it('should resolve styles on the annotation against the moduleUrl',
inject(
[AsyncTestCompleter, DirectiveNormalizer],
(async: AsyncTestCompleter, normalizer: DirectiveNormalizer) => {
normalizer
.normalizeTemplate(dirType, new CompileTemplateMetadata({
encapsulation: null,
template: '',
templateUrl: null,
styles: [],
styleUrls: ['test.css']
}))
.then((template: CompileTemplateMetadata) => {
expect(template.styleUrls).toEqual(['package:some/module/test.css']);
async.done();
});
}));
it('should resolve styles in the template against the moduleUrl',
inject(
[AsyncTestCompleter, DirectiveNormalizer],
(async: AsyncTestCompleter, normalizer: DirectiveNormalizer) => {
normalizer
.normalizeTemplate(dirType, new CompileTemplateMetadata({
encapsulation: null,
template: '<style>@import test.css</style>',
templateUrl: null,
styles: [],
styleUrls: []
}))
.then((template: CompileTemplateMetadata) => {
expect(template.styleUrls).toEqual(['package:some/module/test.css']);
async.done();
});
}));
it('should use ViewEncapsulation.Emulated by default',
inject(
[AsyncTestCompleter, DirectiveNormalizer],
(async: AsyncTestCompleter, normalizer: DirectiveNormalizer) => {
normalizer
.normalizeTemplate(dirType, new CompileTemplateMetadata({
encapsulation: null,
template: '',
templateUrl: null,
styles: [],
styleUrls: ['test.css']
}))
.then((template: CompileTemplateMetadata) => {
expect(template.encapsulation).toEqual(ViewEncapsulation.Emulated);
async.done();
});
}));
it('should use default encapsulation provided by CompilerConfig',
inject(
[AsyncTestCompleter, CompilerConfig, DirectiveNormalizer],
(async: AsyncTestCompleter, config: CompilerConfig,
normalizer: DirectiveNormalizer) => {
config.defaultEncapsulation = ViewEncapsulation.None;
normalizer
.normalizeTemplate(dirType, new CompileTemplateMetadata({
encapsulation: null,
template: '',
templateUrl: null,
styles: [],
styleUrls: ['test.css']
}))
.then((template: CompileTemplateMetadata) => {
expect(template.encapsulation).toEqual(ViewEncapsulation.None);
async.done();
});
}));
});
describe('templateUrl', () => {
it('should load a template from a url that is resolved against moduleUrl',
inject(
[AsyncTestCompleter, DirectiveNormalizer, XHR],
(async: AsyncTestCompleter, normalizer: DirectiveNormalizer, xhr: MockXHR) => {
xhr.expect('package:some/module/sometplurl.html', 'a');
normalizer
.normalizeTemplate(dirType, new CompileTemplateMetadata({
encapsulation: null,
template: null,
templateUrl: 'sometplurl.html',
styles: [],
styleUrls: ['test.css']
}))
.then((template: CompileTemplateMetadata) => {
expect(template.template).toEqual('a');
expect(template.templateUrl).toEqual('package:some/module/sometplurl.html');
async.done();
});
xhr.flush();
}));
it('should resolve styles on the annotation against the moduleUrl',
inject(
[AsyncTestCompleter, DirectiveNormalizer, XHR],
(async: AsyncTestCompleter, normalizer: DirectiveNormalizer, xhr: MockXHR) => {
xhr.expect('package:some/module/tpl/sometplurl.html', '');
normalizer
.normalizeTemplate(dirType, new CompileTemplateMetadata({
encapsulation: null,
template: null,
templateUrl: 'tpl/sometplurl.html',
styles: [],
styleUrls: ['test.css']
}))
.then((template: CompileTemplateMetadata) => {
expect(template.styleUrls).toEqual(['package:some/module/test.css']);
async.done();
});
xhr.flush();
}));
it('should resolve styles in the template against the templateUrl',
inject(
[AsyncTestCompleter, DirectiveNormalizer, XHR],
(async: AsyncTestCompleter, normalizer: DirectiveNormalizer, xhr: MockXHR) => {
xhr.expect(
'package:some/module/tpl/sometplurl.html', '<style>@import test.css</style>');
normalizer
.normalizeTemplate(dirType, new CompileTemplateMetadata({
encapsulation: null,
template: null,
templateUrl: 'tpl/sometplurl.html',
styles: [],
styleUrls: []
}))
.then((template: CompileTemplateMetadata) => {
expect(template.styleUrls).toEqual(['package:some/module/tpl/test.css']);
async.done();
});
xhr.flush();
}));
});
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( expect(() => normalizer.normalizeDirective(new CompileDirectiveMetadata({
() => normalizer.normalizeTemplate( type: dirType,
dirType, isComponent: true,
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}))) template:
.toThrowError('No template specified for component SomeComp'); new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []})
}))).toThrowError('No template specified for component SomeComp');
})); }));
});
describe('normalizeTemplateSync', () => {
it('should store the template',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
let template = normalizer.normalizeTemplateSync(dirType, new CompileTemplateMetadata({
encapsulation: null,
template: 'a',
templateUrl: null,
styles: [],
styleUrls: []
}))
expect(template.template).toEqual('a');
expect(template.templateUrl).toEqual('package:some/module/a.js');
}));
it('should resolve styles on the annotation against the moduleUrl',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
let template = normalizer.normalizeTemplateSync(dirType, new CompileTemplateMetadata({
encapsulation: null,
template: '',
templateUrl: null,
styles: [],
styleUrls: ['test.css']
}))
expect(template.styleUrls).toEqual(['package:some/module/test.css']);
}));
it('should resolve styles in the template against the moduleUrl',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
let template =
normalizer.normalizeTemplateSync(dirType, new CompileTemplateMetadata({
encapsulation: null,
template: '<style>@import test.css</style>',
templateUrl: null,
styles: [],
styleUrls: []
}))
expect(template.styleUrls).toEqual(['package:some/module/test.css']);
}));
it('should use ViewEncapsulation.Emulated by default',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
let template = normalizer.normalizeTemplateSync(dirType, new CompileTemplateMetadata({
encapsulation: null,
template: '',
templateUrl: null,
styles: [],
styleUrls: ['test.css']
}))
expect(template.encapsulation).toEqual(ViewEncapsulation.Emulated);
}));
it('should use default encapsulation provided by CompilerConfig',
inject(
[CompilerConfig, DirectiveNormalizer],
(config: CompilerConfig, normalizer: DirectiveNormalizer) => {
config.defaultEncapsulation = ViewEncapsulation.None;
let template =
normalizer.normalizeTemplateSync(dirType, new CompileTemplateMetadata({
encapsulation: null,
template: '',
templateUrl: null,
styles: [],
styleUrls: ['test.css']
}))
expect(template.encapsulation).toEqual(ViewEncapsulation.None);
}));
});
describe('templateUrl', () => {
it('should load a template from a url that is resolved against moduleUrl',
inject(
[AsyncTestCompleter, DirectiveNormalizer, XHR],
(async: AsyncTestCompleter, normalizer: DirectiveNormalizer, xhr: MockXHR) => {
xhr.expect('package:some/module/sometplurl.html', 'a');
normalizer
.normalizeTemplateAsync(dirType, new CompileTemplateMetadata({
encapsulation: null,
template: null,
templateUrl: 'sometplurl.html',
styles: [],
styleUrls: ['test.css']
}))
.then((template: CompileTemplateMetadata) => {
expect(template.template).toEqual('a');
expect(template.templateUrl).toEqual('package:some/module/sometplurl.html');
async.done();
});
xhr.flush();
}));
it('should resolve styles on the annotation against the moduleUrl',
inject(
[AsyncTestCompleter, DirectiveNormalizer, XHR],
(async: AsyncTestCompleter, normalizer: DirectiveNormalizer, xhr: MockXHR) => {
xhr.expect('package:some/module/tpl/sometplurl.html', '');
normalizer
.normalizeTemplateAsync(dirType, new CompileTemplateMetadata({
encapsulation: null,
template: null,
templateUrl: 'tpl/sometplurl.html',
styles: [],
styleUrls: ['test.css']
}))
.then((template: CompileTemplateMetadata) => {
expect(template.styleUrls).toEqual(['package:some/module/test.css']);
async.done();
});
xhr.flush();
}));
it('should resolve styles in the template against the templateUrl',
inject(
[AsyncTestCompleter, DirectiveNormalizer, XHR],
(async: AsyncTestCompleter, normalizer: DirectiveNormalizer, xhr: MockXHR) => {
xhr.expect(
'package:some/module/tpl/sometplurl.html', '<style>@import test.css</style>');
normalizer
.normalizeTemplateAsync(dirType, new CompileTemplateMetadata({
encapsulation: null,
template: null,
templateUrl: 'tpl/sometplurl.html',
styles: [],
styleUrls: []
}))
.then((template: CompileTemplateMetadata) => {
expect(template.styleUrls).toEqual(['package:some/module/tpl/test.css']);
async.done();
});
xhr.flush();
}));
});
describe('normalizeExternalStylesheets', () => {
beforeEachProviders(() => [{provide: XHR, useClass: SpyXHR}]);
it('should load an external stylesheet',
inject(
[AsyncTestCompleter, DirectiveNormalizer, XHR],
(async: AsyncTestCompleter, normalizer: DirectiveNormalizer, xhr: SpyXHR) => {
programXhrSpy(xhr, {'package:some/module/test.css': 'a'});
normalizer
.normalizeExternalStylesheets(new CompileTemplateMetadata({
template: '',
templateUrl: '',
styleUrls: ['package:some/module/test.css']
}))
.then((template: CompileTemplateMetadata) => {
expect(template.externalStylesheets.length).toBe(1);
expect(template.externalStylesheets[0]).toEqual(new CompileStylesheetMetadata({
moduleUrl: 'package:some/module/test.css',
styles: ['a'],
styleUrls: []
}));
async.done();
});
}));
it('should load stylesheets referenced by external stylesheets',
inject(
[AsyncTestCompleter, DirectiveNormalizer, XHR],
(async: AsyncTestCompleter, normalizer: DirectiveNormalizer, xhr: SpyXHR) => {
programXhrSpy(xhr, {
'package:some/module/test.css': 'a@import "test2.css"',
'package:some/module/test2.css': 'b'
});
normalizer
.normalizeExternalStylesheets(new CompileTemplateMetadata({
template: '',
templateUrl: '',
styleUrls: ['package:some/module/test.css']
}))
.then((template: CompileTemplateMetadata) => {
expect(template.externalStylesheets.length).toBe(2);
expect(template.externalStylesheets[0]).toEqual(new CompileStylesheetMetadata({
moduleUrl: 'package:some/module/test.css',
styles: ['a'],
styleUrls: ['package:some/module/test2.css']
}));
expect(template.externalStylesheets[1]).toEqual(new CompileStylesheetMetadata({
moduleUrl: 'package:some/module/test2.css',
styles: ['b'],
styleUrls: []
}));
async.done();
});
}));
});
describe('caching', () => {
it('should work for templateUrl',
inject(
[AsyncTestCompleter, DirectiveNormalizer, XHR],
(async: AsyncTestCompleter, normalizer: DirectiveNormalizer, xhr: MockXHR) => {
xhr.expect('package:some/module/cmp.html', 'a');
var templateMeta = new CompileTemplateMetadata({
templateUrl: 'cmp.html',
});
Promise
.all([
normalizer.normalizeTemplateAsync(dirType, templateMeta),
normalizer.normalizeTemplateAsync(dirType, templateMeta)
])
.then((templates: CompileTemplateMetadata[]) => {
expect(templates[0].template).toEqual('a');
expect(templates[1].template).toEqual('a');
async.done();
});
xhr.flush();
}));
}); });
@ -369,3 +424,14 @@ export function main() {
}); });
}); });
} }
function programXhrSpy(spy: SpyXHR, results: {[key: string]: string}) {
spy.spy('get').andCallFake((url: string): Promise<any> => {
var result = results[url];
if (result) {
return Promise.resolve(result);
} else {
return Promise.reject(`Unknown mock url ${url}`);
}
});
}

View File

@ -20,11 +20,11 @@ import {TemplateParser} from '@angular/compiler/src/template_parser';
import {createOfflineCompileUrlResolver} from '@angular/compiler/src/url_resolver'; import {createOfflineCompileUrlResolver} from '@angular/compiler/src/url_resolver';
import {MODULE_SUFFIX} from '@angular/compiler/src/util'; import {MODULE_SUFFIX} from '@angular/compiler/src/util';
import {ViewCompiler} from '@angular/compiler/src/view_compiler/view_compiler'; import {ViewCompiler} from '@angular/compiler/src/view_compiler/view_compiler';
import {XHR} from '@angular/compiler/src/xhr';
import {Console} from '../core_private'; import {Console} from '../core_private';
import {IS_DART, isPresent, print} from '../src/facade/lang'; import {IS_DART, isPresent, print} from '../src/facade/lang';
import {MockSchemaRegistry} from '../testing/schema_registry_mock'; import {MockSchemaRegistry} from '../testing/schema_registry_mock';
import {MockXHR} from '../testing/xhr_mock';
export class CompA { user: string; } export class CompA { user: string; }
@ -44,9 +44,8 @@ export var compAMetadata = CompileDirectiveMetadata.create({
}) })
}); });
function _createOfflineCompiler(xhr: MockXHR, emitter: OutputEmitter): OfflineCompiler { function _createOfflineCompiler(xhr: XHR, emitter: OutputEmitter): OfflineCompiler {
var urlResolver = createOfflineCompileUrlResolver(); var urlResolver = createOfflineCompileUrlResolver();
xhr.when(`${THIS_MODULE_PATH}/offline_compiler_compa.html`, 'Hello World {{user}}!');
var htmlParser = new HtmlParser(); var htmlParser = new HtmlParser();
var config = new CompilerConfig({genDebugInfo: true, useJit: true}); var config = new CompilerConfig({genDebugInfo: true, useJit: true});
var normalizer = new DirectiveNormalizer(xhr, urlResolver, htmlParser, config); var normalizer = new DirectiveNormalizer(xhr, urlResolver, htmlParser, config);
@ -54,24 +53,25 @@ function _createOfflineCompiler(xhr: MockXHR, emitter: OutputEmitter): OfflineCo
normalizer, normalizer,
new TemplateParser( new TemplateParser(
new Parser(new Lexer()), new MockSchemaRegistry({}, {}), htmlParser, new Console(), []), new Parser(new Lexer()), new MockSchemaRegistry({}, {}), htmlParser, new Console(), []),
new StyleCompiler(urlResolver), new ViewCompiler(config), emitter, xhr); new StyleCompiler(urlResolver), new ViewCompiler(config), emitter);
} }
export function compileComp( export function compileComp(
emitter: OutputEmitter, comp: CompileDirectiveMetadata): Promise<string> { emitter: OutputEmitter, comp: CompileDirectiveMetadata): Promise<string> {
var xhr = new MockXHR(); var xhr = new MockXhr();
xhr.setResult(`${THIS_MODULE_PATH}/offline_compiler_compa.html`, '');
xhr.setResult(`${THIS_MODULE_PATH}/offline_compiler_compa.css`, '');
var compiler = _createOfflineCompiler(xhr, emitter); var compiler = _createOfflineCompiler(xhr, emitter);
var result = compiler.normalizeDirectiveMetadata(comp).then((normComp) => { var result = compiler.normalizeDirectiveMetadata(comp).then((normComp) => {
return compiler.compileTemplates([new NormalizedComponentWithViewDirectives(normComp, [], [])]) return compiler
.compileTemplates([new NormalizedComponentWithViewDirectives(normComp, [], [])])[0]
.source; .source;
}); });
xhr.flush();
return result; return result;
} }
export class SimpleJsImportGenerator implements ImportGenerator { export class SimpleJsImportGenerator implements ImportGenerator {
getImportPath(moduleUrlStr: string, importedUrlStr: string): string { getImportPath(moduleUrlStr: string, importedUrlStr: string): string {
// var moduleAssetUrl = ImportGenerator.parseAssetUrl(moduleUrlStr);
var importedAssetUrl = ImportGenerator.parseAssetUrl(importedUrlStr); var importedAssetUrl = ImportGenerator.parseAssetUrl(importedUrlStr);
if (isPresent(importedAssetUrl)) { if (isPresent(importedAssetUrl)) {
return `${importedAssetUrl.packageName}/${importedAssetUrl.modulePath}`; return `${importedAssetUrl.packageName}/${importedAssetUrl.modulePath}`;
@ -80,3 +80,15 @@ export class SimpleJsImportGenerator implements ImportGenerator {
} }
} }
} }
class MockXhr implements XHR {
results: {[key: string]: string} = {};
setResult(url: string, content: string) { this.results[url] = content; }
get(url: string): Promise<string> {
if (url in this.results) {
return Promise.resolve(this.results[url]);
}
return Promise.reject<any>(`Unknown url ${url}`);
}
}

View File

@ -0,0 +1,108 @@
import {beforeEach, ddescribe, xdescribe, describe, expect, iit, inject, beforeEachProviders, it, xit,} from '@angular/core/testing/testing_internal';
import {Injectable, Component, Input, ViewMetadata, Compiler, ComponentFactory, Injector} from '@angular/core';
import {ConcreteType, stringify} from '../src/facade/lang';
import {fakeAsync, tick, TestComponentBuilder, ComponentFixture} from '@angular/core/testing';
import {XHR, ViewResolver} from '@angular/compiler';
import {MockViewResolver} from '@angular/compiler/testing';
import {SpyXHR} from './spies';
@Component({selector: 'child-cmp', template: 'childComp'})
class ChildComp {
}
@Component({selector: 'some-cmp', template: 'someComp'})
class SomeComp {
}
@Component({selector: 'some-cmp', templateUrl: './someTpl'})
class SomeCompWithUrlTemplate {
}
export function main() {
describe('RuntimeCompiler', () => {
let compiler: Compiler;
let xhr: SpyXHR;
let tcb: TestComponentBuilder;
let viewResolver: MockViewResolver;
let injector: Injector;
beforeEachProviders(() => [{provide: XHR, useValue: new SpyXHR()}]);
beforeEach(inject(
[Compiler, TestComponentBuilder, XHR, ViewResolver, Injector],
(_compiler: Compiler, _tcb: TestComponentBuilder, _xhr: SpyXHR,
_viewResolver: MockViewResolver, _injector: Injector) => {
compiler = _compiler;
tcb = _tcb;
xhr = _xhr;
viewResolver = _viewResolver;
injector = _injector;
}));
describe('clearCacheFor', () => {
it('should support changing the content of a template referenced via templateUrl',
fakeAsync(() => {
xhr.spy('get').andCallFake(() => Promise.resolve('init'));
let compFixture =
tcb.overrideView(SomeComp, new ViewMetadata({templateUrl: '/myComp.html'}))
.createFakeAsync(SomeComp);
expect(compFixture.nativeElement).toHaveText('init');
xhr.spy('get').andCallFake(() => Promise.resolve('new content'));
// Note: overrideView is calling .clearCacheFor...
compFixture = tcb.overrideView(SomeComp, new ViewMetadata({templateUrl: '/myComp.html'}))
.createFakeAsync(SomeComp);
expect(compFixture.nativeElement).toHaveText('new content');
}));
it('should support overwriting inline templates', () => {
let componentFixture = tcb.createSync(SomeComp);
expect(componentFixture.nativeElement).toHaveText('someComp');
componentFixture = tcb.overrideTemplate(SomeComp, 'test').createSync(SomeComp);
expect(componentFixture.nativeElement).toHaveText('test');
});
it('should not update existing compilation results', () => {
viewResolver.setView(
SomeComp,
new ViewMetadata({template: '<child-cmp></child-cmp>', directives: [ChildComp]}));
viewResolver.setInlineTemplate(ChildComp, 'oldChild');
let compFactory = compiler.compileComponentSync(SomeComp);
viewResolver.setInlineTemplate(ChildComp, 'newChild');
compiler.compileComponentSync(SomeComp);
let compRef = compFactory.create(injector);
expect(compRef.location.nativeElement).toHaveText('oldChild');
});
});
describe('compileComponentSync', () => {
it('should throw when using a templateUrl that has not been compiled before', () => {
xhr.spy('get').andCallFake(() => Promise.resolve(''));
expect(() => tcb.createSync(SomeCompWithUrlTemplate))
.toThrowError(
`Can't compile synchronously as ${stringify(SomeCompWithUrlTemplate)} is still being loaded!`);
});
it('should throw when using a templateUrl in a nested component that has not been compiled before',
() => {
xhr.spy('get').andCallFake(() => Promise.resolve(''));
let localTcb =
tcb.overrideView(SomeComp, new ViewMetadata({template: '', directives: [ChildComp]}))
.overrideView(ChildComp, new ViewMetadata({templateUrl: '/someTpl.html'}));
expect(() => localTcb.createSync(SomeComp))
.toThrowError(
`Can't compile synchronously as ${stringify(ChildComp)} is still being loaded!`);
});
it('should allow to use templateUrl components that have been loaded before',
fakeAsync(() => {
xhr.spy('get').andCallFake(() => Promise.resolve('hello'));
tcb.createFakeAsync(SomeCompWithUrlTemplate);
let compFixture = tcb.createSync(SomeCompWithUrlTemplate);
expect(compFixture.nativeElement).toHaveText('hello');
}));
});
});
}

View File

@ -9,7 +9,7 @@
import {beforeEach, ddescribe, xdescribe, describe, expect, iit, inject, beforeEachProviders, it, xit,} from '@angular/core/testing/testing_internal'; import {beforeEach, ddescribe, xdescribe, describe, expect, iit, inject, beforeEachProviders, it, xit,} from '@angular/core/testing/testing_internal';
import {TestComponentBuilder, ComponentFixtureAutoDetect, ComponentFixtureNoNgZone} from '@angular/compiler/testing'; import {TestComponentBuilder, ComponentFixtureAutoDetect, ComponentFixtureNoNgZone} from '@angular/compiler/testing';
import {AsyncTestCompleter} from '@angular/core/testing/testing_internal'; import {AsyncTestCompleter} from '@angular/core/testing/testing_internal';
import {Injectable, Component, Input, ViewMetadata, ComponentResolver} from '@angular/core'; import {Injectable, Component, Input, ViewMetadata} from '@angular/core';
import {NgIf} from '@angular/common'; import {NgIf} from '@angular/common';
import {TimerWrapper} from '../src/facade/async'; import {TimerWrapper} from '../src/facade/async';
import {IS_DART} from '../src/facade/lang'; import {IS_DART} from '../src/facade/lang';
@ -320,6 +320,15 @@ export function main() {
}); });
})); }));
it('should create components synchronously',
inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
let componentFixture =
tcb.overrideTemplate(MockChildComp, '<span>Mock</span>').createSync(MockChildComp);
componentFixture.detectChanges();
expect(componentFixture.nativeElement).toHaveText('Mock');
}));
if (!IS_DART) { if (!IS_DART) {
describe('ComponentFixture', () => { describe('ComponentFixture', () => {
it('should auto detect changes if autoDetectChanges is called', it('should auto detect changes if autoDetectChanges is called',
@ -604,27 +613,6 @@ export function main() {
})); }));
}); });
describe('createSync', () => {
it('should create components',
inject(
[ComponentResolver, TestComponentBuilder, AsyncTestCompleter],
(cr: ComponentResolver, tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
cr.resolveComponent(MyIfComp).then((cmpFactory) => {
let componentFixture = tcb.createSync(cmpFactory);
componentFixture.detectChanges();
expect(componentFixture.nativeElement).toHaveText('MyIf()');
componentFixture.componentInstance.showMore = true;
componentFixture.detectChanges();
expect(componentFixture.nativeElement).toHaveText('MyIf(More)');
async.done();
});
}));
});
}); });
} }
}); });

View File

@ -6,18 +6,19 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {beforeEach, ddescribe, describe, expect, iit, it,} from '@angular/core/testing/testing_internal'; import {beforeEach, ddescribe, describe, expect, iit, it, inject,} from '@angular/core/testing/testing_internal';
import {stringify} from '../src/facade/lang'; import {stringify} from '../src/facade/lang';
import {MockViewResolver} from '../testing'; import {MockViewResolver} from '../testing';
import {Component, ViewMetadata} from '@angular/core'; import {Component, ViewMetadata, Injector} from '@angular/core';
import {isBlank} from '../src/facade/lang'; import {isBlank} from '../src/facade/lang';
export function main() { export function main() {
describe('MockViewResolver', () => { describe('MockViewResolver', () => {
var viewResolver: MockViewResolver; var viewResolver: MockViewResolver;
beforeEach(() => { viewResolver = new MockViewResolver(); }); beforeEach(inject(
[Injector], (injector: Injector) => { viewResolver = new MockViewResolver(injector); }));
describe('View overriding', () => { describe('View overriding', () => {
it('should fallback to the default ViewResolver when templates are not overridden', () => { it('should fallback to the default ViewResolver when templates are not overridden', () => {
@ -33,13 +34,12 @@ export function main() {
expect(isBlank(view.directives)).toBe(true); expect(isBlank(view.directives)).toBe(true);
}); });
it('should not allow overriding a view after it has been resolved', () => { it('should allow overriding a view after it has been resolved', () => {
viewResolver.resolve(SomeComponent); viewResolver.resolve(SomeComponent);
expect(() => { viewResolver.setView(SomeComponent, new ViewMetadata({template: 'overridden template'}));
viewResolver.setView(SomeComponent, new ViewMetadata({template: 'overridden template'})); var view = viewResolver.resolve(SomeComponent);
}) expect(view.template).toEqual('overridden template');
.toThrowError( expect(isBlank(view.directives)).toBe(true);
`The component ${stringify(SomeComponent)} has already been compiled, its configuration can not be changed`);
}); });
}); });
@ -58,11 +58,11 @@ export function main() {
expect(view.template).toEqual('overridden template x 2'); expect(view.template).toEqual('overridden template x 2');
}); });
it('should not allow overriding a view after it has been resolved', () => { it('should allow overriding a view after it has been resolved', () => {
viewResolver.resolve(SomeComponent); viewResolver.resolve(SomeComponent);
expect(() => { viewResolver.setInlineTemplate(SomeComponent, 'overridden template'); }) viewResolver.setInlineTemplate(SomeComponent, 'overridden template');
.toThrowError( var view = viewResolver.resolve(SomeComponent);
`The component ${stringify(SomeComponent)} has already been compiled, its configuration can not be changed`); expect(view.template).toEqual('overridden template');
}); });
}); });
@ -90,13 +90,12 @@ export function main() {
`Overriden directive ${stringify(SomeOtherDirective)} not found in the template of ${stringify(SomeComponent)}`); `Overriden directive ${stringify(SomeOtherDirective)} not found in the template of ${stringify(SomeComponent)}`);
}); });
it('should not allow overriding a directive after its view has been resolved', () => { it('should allow overriding a directive after its view has been resolved', () => {
viewResolver.resolve(SomeComponent); viewResolver.resolve(SomeComponent);
expect(() => { viewResolver.overrideViewDirective(SomeComponent, SomeDirective, SomeOtherDirective);
viewResolver.overrideViewDirective(SomeComponent, SomeDirective, SomeOtherDirective); var view = viewResolver.resolve(SomeComponent);
}) expect(view.directives.length).toEqual(1);
.toThrowError( expect(view.directives[0]).toBe(SomeOtherDirective);
`The component ${stringify(SomeComponent)} has already been compiled, its configuration can not be changed`);
}); });
}); });
}); });

View File

@ -6,13 +6,14 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ComponentMetadata, DirectiveMetadata, Injectable} from '@angular/core'; import {Compiler, ComponentMetadata, DirectiveMetadata, Injectable, Injector} from '@angular/core';
import {DirectiveResolver} from '../src/directive_resolver'; import {DirectiveResolver} from '../src/directive_resolver';
import {Map} from '../src/facade/collection'; import {Map} from '../src/facade/collection';
import {Type, isPresent} from '../src/facade/lang'; import {Type, isPresent} from '../src/facade/lang';
/** /**
* An implementation of {@link DirectiveResolver} that allows overriding * An implementation of {@link DirectiveResolver} that allows overriding
* various properties of directives. * various properties of directives.
@ -22,6 +23,10 @@ export class MockDirectiveResolver extends DirectiveResolver {
private _providerOverrides = new Map<Type, any[]>(); private _providerOverrides = new Map<Type, any[]>();
private viewProviderOverrides = new Map<Type, any[]>(); private viewProviderOverrides = new Map<Type, any[]>();
constructor(private _injector: Injector) { super(); }
private get _compiler(): Compiler { return this._injector.get(Compiler); }
resolve(type: Type): DirectiveMetadata { resolve(type: Type): DirectiveMetadata {
var dm = super.resolve(type); var dm = super.resolve(type);
@ -69,9 +74,11 @@ export class MockDirectiveResolver extends DirectiveResolver {
setProvidersOverride(type: Type, providers: any[]): void { setProvidersOverride(type: Type, providers: any[]): void {
this._providerOverrides.set(type, providers); this._providerOverrides.set(type, providers);
this._compiler.clearCacheFor(type);
} }
setViewProvidersOverride(type: Type, viewProviders: any[]): void { setViewProvidersOverride(type: Type, viewProviders: any[]): void {
this.viewProviderOverrides.set(type, viewProviders); this.viewProviderOverrides.set(type, viewProviders);
this._compiler.clearCacheFor(type);
} }
} }

View File

@ -6,39 +6,36 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AnimationEntryMetadata, ComponentFactory, ComponentResolver, Injectable, Injector, NgZone, ViewMetadata} from '@angular/core'; import {AnimationEntryMetadata, Compiler, ComponentFactory, Injectable, Injector, NgZone, ViewMetadata} from '@angular/core';
import {ComponentFixture, ComponentFixtureNoNgZone, TestComponentBuilder} from '@angular/core/testing'; import {ComponentFixture, ComponentFixtureNoNgZone, TestComponentBuilder} from '@angular/core/testing';
import {DirectiveResolver, ViewResolver} from '../index'; import {DirectiveResolver, ViewResolver} from '../index';
import {MapWrapper} from '../src/facade/collection'; import {MapWrapper} from '../src/facade/collection';
import {IS_DART, Type, isPresent} from '../src/facade/lang'; import {ConcreteType, IS_DART, Type, isPresent} from '../src/facade/lang';
/** /**
* @deprecated Import TestComponentRenderer from @angular/core/testing * @deprecated Import TestComponentRenderer from @angular/core/testing
*/ */
export {TestComponentRenderer} from '@angular/core/testing'; export {TestComponentRenderer} from '@angular/core/testing';
/** /**
* @deprecated Import TestComponentBuilder from @angular/core/testing * @deprecated Import TestComponentBuilder from @angular/core/testing
*/ */
export {TestComponentBuilder} from '@angular/core/testing'; export {TestComponentBuilder} from '@angular/core/testing';
/** /**
* @deprecated Import ComponentFixture from @angular/core/testing * @deprecated Import ComponentFixture from @angular/core/testing
*/ */
export {ComponentFixture} from '@angular/core/testing'; export {ComponentFixture} from '@angular/core/testing';
/** /**
* @deprecated Import ComponentFixtureNoNgZone from @angular/core/testing * @deprecated Import ComponentFixtureNoNgZone from @angular/core/testing
*/ */
export {ComponentFixtureNoNgZone} from '@angular/core/testing'; export {ComponentFixtureNoNgZone} from '@angular/core/testing';
/** /**
* @deprecated Import ComponentFixtureAutoDetect from @angular/core/testing * @deprecated Import ComponentFixtureAutoDetect from @angular/core/testing
*/ */
export {ComponentFixtureAutoDetect} from '@angular/core/testing'; export {ComponentFixtureAutoDetect} from '@angular/core/testing';
/** /**
* A TestComponentBuilder that allows overriding based on the compiler. * A TestComponentBuilder that allows overriding based on the compiler.
*/ */
@ -114,32 +111,31 @@ export class OverridingTestComponentBuilder extends TestComponentBuilder {
return clone; return clone;
} }
createAsync(rootComponentType: Type): Promise<ComponentFixture<any>> { createAsync<T>(rootComponentType: ConcreteType<T>): Promise<ComponentFixture<T>> {
let noNgZone = IS_DART || this._injector.get(ComponentFixtureNoNgZone, false); this._applyMetadataOverrides();
let ngZone: NgZone = noNgZone ? null : this._injector.get(NgZone, null); return super.createAsync(rootComponentType);
}
let initComponent = () => { createSync<T>(rootComponentType: ConcreteType<T>): ComponentFixture<T> {
let mockDirectiveResolver = this._injector.get(DirectiveResolver); this._applyMetadataOverrides();
let mockViewResolver = this._injector.get(ViewResolver); return super.createSync(rootComponentType);
this._viewOverrides.forEach((view, type) => mockViewResolver.setView(type, view)); }
this._templateOverrides.forEach(
(template, type) => mockViewResolver.setInlineTemplate(type, template));
this._animationOverrides.forEach(
(animationsEntry, type) => mockViewResolver.setAnimations(type, animationsEntry));
this._directiveOverrides.forEach((overrides, component) => {
overrides.forEach(
(to, from) => { mockViewResolver.overrideViewDirective(component, from, to); });
});
this._bindingsOverrides.forEach(
(bindings, type) => mockDirectiveResolver.setProvidersOverride(type, bindings));
this._viewBindingsOverrides.forEach(
(bindings, type) => mockDirectiveResolver.setViewProvidersOverride(type, bindings));
let promise: Promise<ComponentFactory<any>> = private _applyMetadataOverrides() {
this._injector.get(ComponentResolver).resolveComponent(rootComponentType); let mockDirectiveResolver = this._injector.get(DirectiveResolver);
return promise.then(componentFactory => this.createFromFactory(ngZone, componentFactory)); let mockViewResolver = this._injector.get(ViewResolver);
}; this._viewOverrides.forEach((view, type) => { mockViewResolver.setView(type, view); });
this._templateOverrides.forEach(
return ngZone == null ? initComponent() : ngZone.run(initComponent); (template, type) => mockViewResolver.setInlineTemplate(type, template));
this._animationOverrides.forEach(
(animationsEntry, type) => mockViewResolver.setAnimations(type, animationsEntry));
this._directiveOverrides.forEach((overrides, component) => {
overrides.forEach(
(to, from) => { mockViewResolver.overrideViewDirective(component, from, to); });
});
this._bindingsOverrides.forEach(
(bindings, type) => mockDirectiveResolver.setProvidersOverride(type, bindings));
this._viewBindingsOverrides.forEach(
(bindings, type) => mockDirectiveResolver.setViewProvidersOverride(type, bindings));
} }
} }

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AnimationEntryMetadata, BaseException, Injectable, Type, ViewMetadata, resolveForwardRef} from '@angular/core'; import {AnimationEntryMetadata, BaseException, Compiler, Injectable, Injector, Type, ViewMetadata, resolveForwardRef} from '@angular/core';
import {ViewResolver} from '../index'; import {ViewResolver} from '../index';
import {Map} from '../src/facade/collection'; import {Map} from '../src/facade/collection';
@ -21,38 +21,38 @@ export class MockViewResolver extends ViewResolver {
/** @internal */ /** @internal */
_animations = new Map<Type, AnimationEntryMetadata[]>(); _animations = new Map<Type, AnimationEntryMetadata[]>();
/** @internal */ /** @internal */
_viewCache = new Map<Type, ViewMetadata>();
/** @internal */
_directiveOverrides = new Map<Type, Map<Type, Type>>(); _directiveOverrides = new Map<Type, Map<Type, Type>>();
constructor() { super(); } constructor(private _injector: Injector) { super(); }
private get _compiler(): Compiler { return this._injector.get(Compiler); }
private _clearCacheFor(component: Type) { this._compiler.clearCacheFor(component); }
/** /**
* Overrides the {@link ViewMetadata} for a component. * Overrides the {@link ViewMetadata} for a component.
*/ */
setView(component: Type, view: ViewMetadata): void { setView(component: Type, view: ViewMetadata): void {
this._checkOverrideable(component);
this._views.set(component, view); this._views.set(component, view);
this._clearCacheFor(component);
} }
/** /**
* Overrides the inline template for a component - other configuration remains unchanged. * Overrides the inline template for a component - other configuration remains unchanged.
*/ */
setInlineTemplate(component: Type, template: string): void { setInlineTemplate(component: Type, template: string): void {
this._checkOverrideable(component);
this._inlineTemplates.set(component, template); this._inlineTemplates.set(component, template);
this._clearCacheFor(component);
} }
setAnimations(component: Type, animations: AnimationEntryMetadata[]): void { setAnimations(component: Type, animations: AnimationEntryMetadata[]): void {
this._checkOverrideable(component);
this._animations.set(component, animations); this._animations.set(component, animations);
this._clearCacheFor(component);
} }
/** /**
* Overrides a directive from the component {@link ViewMetadata}. * Overrides a directive from the component {@link ViewMetadata}.
*/ */
overrideViewDirective(component: Type, from: Type, to: Type): void { overrideViewDirective(component: Type, from: Type, to: Type): void {
this._checkOverrideable(component);
var overrides = this._directiveOverrides.get(component); var overrides = this._directiveOverrides.get(component);
if (isBlank(overrides)) { if (isBlank(overrides)) {
@ -61,6 +61,7 @@ export class MockViewResolver extends ViewResolver {
} }
overrides.set(from, to); overrides.set(from, to);
this._clearCacheFor(component);
} }
/** /**
@ -72,10 +73,7 @@ export class MockViewResolver extends ViewResolver {
* - Override the @View definition, see `setInlineTemplate`. * - Override the @View definition, see `setInlineTemplate`.
*/ */
resolve(component: Type): ViewMetadata { resolve(component: Type): ViewMetadata {
var view = this._viewCache.get(component); var view = this._views.get(component);
if (isPresent(view)) return view;
view = this._views.get(component);
if (isBlank(view)) { if (isBlank(view)) {
view = super.resolve(component); view = super.resolve(component);
} }
@ -123,26 +121,8 @@ export class MockViewResolver extends ViewResolver {
interpolation: view.interpolation interpolation: view.interpolation
}); });
this._viewCache.set(component, view);
return view; return view;
} }
/**
* @internal
*
* Once a component has been compiled, the AppProtoView is stored in the compiler cache.
*
* Then it should not be possible to override the component configuration after the component
* has been compiled.
*/
_checkOverrideable(component: Type): void {
var cached = this._viewCache.get(component);
if (isPresent(cached)) {
throw new BaseException(
`The component ${stringify(component)} has already been compiled, its configuration can not be changed`);
}
}
} }
function flattenArray(tree: any[], out: Array<Type|any[]>): void { function flattenArray(tree: any[], out: Array<Type|any[]>): void {

View File

@ -7,6 +7,7 @@
*/ */
// Public API for compiler // Public API for compiler
export {Compiler} from './linker/compiler';
export {ComponentFactory, ComponentRef} from './linker/component_factory'; export {ComponentFactory, ComponentRef} from './linker/component_factory';
export {ComponentFactoryResolver, NoComponentFactoryError} from './linker/component_factory_resolver'; export {ComponentFactoryResolver, NoComponentFactoryError} from './linker/component_factory_resolver';
export {ComponentResolver} from './linker/component_resolver'; export {ComponentResolver} from './linker/component_resolver';

View File

@ -0,0 +1,45 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {BaseException} from '../facade/exceptions';
import {ConcreteType, Type, stringify} from '../facade/lang';
import {ComponentFactory} from './component_factory';
/**
* Low-level service for running the angular compiler duirng runtime
* to create {@link ComponentFactory}s, which
* can later be used to create and render a Component instance.
* @stable
*/
export class Compiler {
/**
* Loads the template and styles of a component and returns the associated `ComponentFactory`.
*/
compileComponentAsync<T>(component: ConcreteType<T>): Promise<ComponentFactory<T>> {
throw new BaseException(
`Runtime compiler is not loaded. Tried to compile ${stringify(component)}`);
}
/**
* Compiles the given component. All templates have to be either inline or compiled via
* `compileComponentAsync` before.
*/
compileComponentSync<T>(component: ConcreteType<T>): ComponentFactory<T> {
throw new BaseException(
`Runtime compiler is not loaded. Tried to compile ${stringify(component)}`);
}
/**
* Clears all caches
*/
clearCache(): void {}
/**
* Clears the cache for the given component.
*/
clearCacheFor(compType: Type) {}
}

View File

@ -8,7 +8,7 @@
import {Inject, OpaqueToken, Optional, SkipSelf} from '../di'; import {Inject, OpaqueToken, Optional, SkipSelf} from '../di';
import {BaseException} from '../facade/exceptions'; import {BaseException} from '../facade/exceptions';
import {ClassWithConstructor, stringify} from '../facade/lang'; import {ConcreteType, stringify} from '../facade/lang';
import {ComponentFactory} from './component_factory'; import {ComponentFactory} from './component_factory';
@ -33,7 +33,7 @@ class _NullComponentFactoryResolver implements ComponentFactoryResolver {
*/ */
export abstract class ComponentFactoryResolver { export abstract class ComponentFactoryResolver {
static NULL: ComponentFactoryResolver = new _NullComponentFactoryResolver(); static NULL: ComponentFactoryResolver = new _NullComponentFactoryResolver();
abstract resolveComponentFactory<T>(component: ClassWithConstructor<T>): ComponentFactory<T>; abstract resolveComponentFactory<T>(component: ConcreteType<T>): ComponentFactory<T>;
} }
export class CodegenComponentFactoryResolver implements ComponentFactoryResolver { export class CodegenComponentFactoryResolver implements ComponentFactoryResolver {

View File

@ -19,7 +19,7 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
isReflectionEnabled(): boolean { return true; } isReflectionEnabled(): boolean { return true; }
factory(t: ConcreteType): Function { factory(t: ConcreteType<any>): Function {
switch (t.length) { switch (t.length) {
case 0: case 0:
return () => new t(); return () => new t();

View File

@ -84,7 +84,7 @@ export interface TypeDecorator {
/** /**
* Generate a class from the definition and annotate it with {@link TypeDecorator#annotations}. * Generate a class from the definition and annotate it with {@link TypeDecorator#annotations}.
*/ */
Class(obj: ClassDefinition): ConcreteType; Class(obj: ClassDefinition): ConcreteType<any>;
} }
function extractAnnotation(annotation: any): any { function extractAnnotation(annotation: any): any {
@ -219,7 +219,7 @@ function applyParams(fnOrArray: (Function | any[]), key: string): Function {
* ``` * ```
* @stable * @stable
*/ */
export function Class(clsDef: ClassDefinition): ConcreteType { export function Class(clsDef: ClassDefinition): ConcreteType<any> {
var constructor = applyParams( var constructor = applyParams(
clsDef.hasOwnProperty('constructor') ? clsDef.constructor : undefined, 'constructor'); clsDef.hasOwnProperty('constructor') ? clsDef.constructor : undefined, 'constructor');
var proto = constructor.prototype; var proto = constructor.prototype;
@ -246,7 +246,7 @@ export function Class(clsDef: ClassDefinition): ConcreteType {
(constructor as any /** TODO #9100 */)['overriddenName'] = `class${_nextClassId++}`; (constructor as any /** TODO #9100 */)['overriddenName'] = `class${_nextClassId++}`;
} }
return <ConcreteType>constructor; return <ConcreteType<any>>constructor;
} }
var Reflect = global.Reflect; var Reflect = global.Reflect;

View File

@ -10,7 +10,7 @@ import {TestComponentBuilder} from '@angular/compiler/testing';
import {ComponentFixture, fakeAsync, flushMicrotasks, tick} from '@angular/core/testing'; import {ComponentFixture, fakeAsync, flushMicrotasks, tick} from '@angular/core/testing';
import {afterEach, beforeEach, beforeEachProviders, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal'; import {afterEach, beforeEach, beforeEachProviders, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
import {isBlank, NumberWrapper,} from '../../src/facade/lang'; import {isBlank, NumberWrapper, ConcreteType,} from '../../src/facade/lang';
import {BaseException} from '../../src/facade/exceptions'; import {BaseException} from '../../src/facade/exceptions';
import {StringMapWrapper} from '../../src/facade/collection'; import {StringMapWrapper} from '../../src/facade/collection';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
@ -40,9 +40,9 @@ export function main() {
var renderLog: RenderLog; var renderLog: RenderLog;
var directiveLog: DirectiveLog; var directiveLog: DirectiveLog;
function createCompFixture( function createCompFixture<T>(
template: string, compType: Type = TestComponent, template: string, compType: ConcreteType<T> = <any>TestComponent,
_tcb: TestComponentBuilder = null): ComponentFixture<any> { _tcb: TestComponentBuilder = null): ComponentFixture<T> {
if (isBlank(_tcb)) { if (isBlank(_tcb)) {
_tcb = tcb; _tcb = tcb;
} }
@ -58,18 +58,19 @@ export function main() {
return nodes.map(node => node.injector.get(dirType)); return nodes.map(node => node.injector.get(dirType));
} }
function _bindSimpleProp( function _bindSimpleProp<T>(
bindAttr: string, compType: Type = TestComponent): ComponentFixture<any> { bindAttr: string, compType: ConcreteType<T> = <any>TestComponent): ComponentFixture<T> {
var template = `<div ${bindAttr}></div>`; var template = `<div ${bindAttr}></div>`;
return createCompFixture(template, compType); return createCompFixture(template, compType);
} }
function _bindSimpleValue( function _bindSimpleValue<T>(
expression: any, compType: Type = TestComponent): ComponentFixture<any> { expression: any, compType: ConcreteType<T> = <any>TestComponent): ComponentFixture<T> {
return _bindSimpleProp(`[someProp]='${expression}'`, compType); return _bindSimpleProp(`[someProp]='${expression}'`, compType);
} }
function _bindAndCheckSimpleValue(expression: any, compType: Type = TestComponent): string[] { function _bindAndCheckSimpleValue<T>(
expression: any, compType: ConcreteType<T> = <any>TestComponent): string[] {
var ctx = _bindSimpleValue(expression, compType); var ctx = _bindSimpleValue(expression, compType);
ctx.detectChanges(false); ctx.detectChanges(false);
return renderLog.log; return renderLog.log;

View File

@ -1504,20 +1504,15 @@ function declareTests({useJit}: {useJit: boolean}) {
describe('error handling', () => { describe('error handling', () => {
it('should report a meaningful error when a directive is missing annotation', it('should report a meaningful error when a directive is missing annotation',
inject( inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
[TestComponentBuilder, AsyncTestCompleter], tcb = tcb.overrideView(
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => { MyComp,
tcb = tcb.overrideView( new ViewMetadata({template: '', directives: [SomeDirectiveMissingAnnotation]}));
MyComp,
new ViewMetadata({template: '', directives: [SomeDirectiveMissingAnnotation]}));
PromiseWrapper.catchError(tcb.createAsync(MyComp), (e) => { expect(() => tcb.createAsync(MyComp))
expect(e.message).toEqual( .toThrowError(
`No Directive annotation found on ${stringify(SomeDirectiveMissingAnnotation)}`); `No Directive annotation found on ${stringify(SomeDirectiveMissingAnnotation)}`);
async.done(); }));
return null;
});
}));
it('should report a meaningful error when a component is missing view annotation', it('should report a meaningful error when a component is missing view annotation',
inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
@ -1530,19 +1525,13 @@ function declareTests({useJit}: {useJit: boolean}) {
})); }));
it('should report a meaningful error when a directive is null', it('should report a meaningful error when a directive is null',
inject( inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
[TestComponentBuilder, AsyncTestCompleter], tcb = tcb.overrideView(MyComp, new ViewMetadata({directives: [[null]], template: ''}));
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
tcb =
tcb.overrideView(MyComp, new ViewMetadata({directives: [[null]], template: ''}));
PromiseWrapper.catchError(tcb.createAsync(MyComp), (e) => { expect(() => tcb.createAsync(MyComp))
expect(e.message).toEqual( .toThrowError(
`Unexpected directive value 'null' on the View of component '${stringify(MyComp)}'`); `Unexpected directive value 'null' on the View of component '${stringify(MyComp)}'`);
async.done(); }));
return null;
});
}));
it('should provide an error context when an error happens in DI', it('should provide an error context when an error happens in DI',
inject( inject(
@ -1642,22 +1631,17 @@ function declareTests({useJit}: {useJit: boolean}) {
if (!IS_DART) { if (!IS_DART) {
it('should report a meaningful error when a directive is undefined', it('should report a meaningful error when a directive is undefined',
inject( inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var undefinedValue: any = void(0); var undefinedValue: any = void(0);
tcb = tcb.overrideView( tcb = tcb.overrideView(
MyComp, new ViewMetadata({directives: [undefinedValue], template: ''})); MyComp, new ViewMetadata({directives: [undefinedValue], template: ''}));
PromiseWrapper.catchError(tcb.createAsync(MyComp), (e) => { expect(() => tcb.createAsync(MyComp))
expect(e.message).toEqual( .toThrowError(
`Unexpected directive value 'undefined' on the View of component '${stringify(MyComp)}'`); `Unexpected directive value 'undefined' on the View of component '${stringify(MyComp)}'`);
async.done(); }));
return null;
});
}));
} }
it('should specify a location of an error that happened during change detection (text)', it('should specify a location of an error that happened during change detection (text)',

View File

@ -261,18 +261,14 @@ export function main() {
})); }));
it('should throw with descriptive error when query selectors are not present', it('should throw with descriptive error when query selectors are not present',
inject( inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
[TestComponentBuilder, AsyncTestCompleter], expect(
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => { () => tcb.overrideTemplate(
tcb.overrideTemplate( MyCompBroken0, '<has-null-query-condition></has-null-query-condition>')
MyCompBroken0, '<has-null-query-condition></has-null-query-condition>') .createAsync(MyCompBroken0))
.createAsync(MyCompBroken0) .toThrowError(
.catch((e) => { `Can't construct a query for the property "errorTrigger" of "${stringify(HasNullQueryCondition)}" since the query selector wasn't defined.`);
expect(e.message).toEqual( }));
`Can't construct a query for the property "errorTrigger" of "${stringify(HasNullQueryCondition)}" since the query selector wasn't defined.`);
async.done();
});
}));
}); });
describe('query for TemplateRef', () => { describe('query for TemplateRef', () => {

View File

@ -12,7 +12,7 @@ import {AsyncTestCompleter} from '@angular/core/testing/testing_internal';
import {IS_DART} from '../../src/facade/lang'; import {IS_DART} from '../../src/facade/lang';
import {Component, Pipe, PipeTransform, provide, ViewMetadata, OpaqueToken, Injector} from '@angular/core'; import {Component, Pipe, PipeTransform, provide, ViewMetadata, PLATFORM_PIPES, OpaqueToken, Injector, forwardRef} from '@angular/core';
import {NgIf, NgClass} from '@angular/common'; import {NgIf, NgClass} from '@angular/common';
import {CompilerConfig} from '@angular/compiler'; import {CompilerConfig} from '@angular/compiler';
@ -170,8 +170,7 @@ function declareTests({useJit}: {useJit: boolean}) {
it('should support ngClass before a component and content projection inside of an ngIf', it('should support ngClass before a component and content projection inside of an ngIf',
inject( inject(
[TestComponentBuilder, AsyncTestCompleter], [TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async: any) => {
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
tcb.overrideView( tcb.overrideView(
MyComp1, new ViewMetadata({ MyComp1, new ViewMetadata({
template: `A<cmp-content *ngIf="true" [ngClass]="'red'">B</cmp-content>C`, template: `A<cmp-content *ngIf="true" [ngClass]="'red'">B</cmp-content>C`,
@ -185,6 +184,15 @@ function declareTests({useJit}: {useJit: boolean}) {
}); });
})); }));
it('should handle mutual recursion entered from multiple sides - #7084',
inject(
[TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async: any) => {
tcb.createAsync(FakeRecursiveComp).then((fixture) => {
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('[]');
async.done();
});
}));
}); });
} }
@ -207,3 +215,37 @@ class CustomPipe implements PipeTransform {
@Component({selector: 'cmp-content', template: `<ng-content></ng-content>`}) @Component({selector: 'cmp-content', template: `<ng-content></ng-content>`})
class CmpWithNgContent { class CmpWithNgContent {
} }
@Component({
selector: 'left',
template: `L<right *ngIf="false"></right>`,
directives: [
NgIf,
forwardRef(() => RightComp),
]
})
class LeftComp {
}
@Component({
selector: 'right',
template: `R<left *ngIf="false"></left>`,
directives: [
NgIf,
forwardRef(() => LeftComp),
]
})
class RightComp {
}
@Component({
selector: 'fakeRecursiveComp',
template: `[<left *ngIf="false"></left><right *ngIf="false"></right>]`,
directives: [
NgIf,
forwardRef(() => LeftComp),
forwardRef(() => RightComp),
]
})
export class FakeRecursiveComp {
}

View File

@ -9,7 +9,7 @@
import {describe, ddescribe, it, iit, xit, xdescribe, expect, beforeEach, beforeEachProviders, inject,} from '@angular/core/testing/testing_internal'; import {describe, ddescribe, it, iit, xit, xdescribe, expect, beforeEach, beforeEachProviders, inject,} from '@angular/core/testing/testing_internal';
import {fakeAsync, flushMicrotasks, tick, ComponentFixture} from '@angular/core/testing'; import {fakeAsync, flushMicrotasks, tick, ComponentFixture} from '@angular/core/testing';
import {TestComponentBuilder} from '@angular/compiler/testing'; import {TestComponentBuilder} from '@angular/compiler/testing';
import {isBlank} from '../../src/facade/lang'; import {isBlank, ConcreteType} from '../../src/facade/lang';
import {Type, ViewContainerRef, TemplateRef, ElementRef, ChangeDetectorRef, ChangeDetectionStrategy, Directive, Component, DebugElement, forwardRef, Input, PipeTransform, Attribute, ViewMetadata, provide, Optional, Inject, Self, InjectMetadata, Pipe, Host, SkipSelfMetadata} from '@angular/core'; import {Type, ViewContainerRef, TemplateRef, ElementRef, ChangeDetectorRef, ChangeDetectionStrategy, Directive, Component, DebugElement, forwardRef, Input, PipeTransform, Attribute, ViewMetadata, provide, Optional, Inject, Self, InjectMetadata, Pipe, Host, SkipSelfMetadata} from '@angular/core';
import {NgIf, NgFor} from '@angular/common'; import {NgIf, NgFor} from '@angular/common';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
@ -242,10 +242,11 @@ class TestComp {
export function main() { export function main() {
var tcb: TestComponentBuilder; var tcb: TestComponentBuilder;
function createCompFixture( function createCompFixture<T>(
template: string, tcb: TestComponentBuilder, comp: Type = null): ComponentFixture<any> { template: string, tcb: TestComponentBuilder,
comp: ConcreteType<T> = null): ComponentFixture<T> {
if (isBlank(comp)) { if (isBlank(comp)) {
comp = TestComp; comp = <any>TestComp;
} }
return tcb return tcb
.overrideView( .overrideView(
@ -255,7 +256,7 @@ export function main() {
} }
function createComp( function createComp(
template: string, tcb: TestComponentBuilder, comp: Type = null): DebugElement { template: string, tcb: TestComponentBuilder, comp: ConcreteType<any> = null): DebugElement {
var fixture = createCompFixture(template, tcb, comp); var fixture = createCompFixture(template, tcb, comp);
fixture.detectChanges(); fixture.detectChanges();
return fixture.debugElement; return fixture.debugElement;

View File

@ -6,14 +6,15 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AnimationEntryMetadata, ComponentFactory, ComponentResolver, Injectable, Injector, NgZone, OpaqueToken, ViewMetadata} from '../index'; import {AnimationEntryMetadata, Compiler, ComponentFactory, Injectable, Injector, NgZone, OpaqueToken, ViewMetadata} from '../index';
import {PromiseWrapper} from '../src/facade/async'; import {PromiseWrapper} from '../src/facade/async';
import {IS_DART, Type, isPresent} from '../src/facade/lang'; import {ConcreteType, IS_DART, Type, isPresent} from '../src/facade/lang';
import {ComponentFixture} from './component_fixture'; import {ComponentFixture} from './component_fixture';
import {tick} from './fake_async'; import {tick} from './fake_async';
/** /**
* An abstract class for inserting the root test component element in a platform independent way. * An abstract class for inserting the root test component element in a platform independent way.
* *
@ -118,20 +119,21 @@ export class TestComponentBuilder {
/** /**
* Builds and returns a ComponentFixture. * Builds and returns a ComponentFixture.
*/ */
createAsync(rootComponentType: Type): Promise<ComponentFixture<any>> { createAsync<T>(rootComponentType: ConcreteType<T>): Promise<ComponentFixture<T>> {
let noNgZone = IS_DART || this._injector.get(ComponentFixtureNoNgZone, false); let noNgZone = IS_DART || this._injector.get(ComponentFixtureNoNgZone, false);
let ngZone: NgZone = noNgZone ? null : this._injector.get(NgZone, null); let ngZone: NgZone = noNgZone ? null : this._injector.get(NgZone, null);
let compiler: Compiler = this._injector.get(Compiler);
let initComponent = () => { let initComponent = () => {
let promise: Promise<ComponentFactory<any>> = let promise: Promise<ComponentFactory<any>> =
this._injector.get(ComponentResolver).resolveComponent(rootComponentType); compiler.compileComponentAsync(rootComponentType);
return promise.then(componentFactory => this.createFromFactory(ngZone, componentFactory)); return promise.then(componentFactory => this.createFromFactory(ngZone, componentFactory));
}; };
return ngZone == null ? initComponent() : ngZone.run(initComponent); return ngZone == null ? initComponent() : ngZone.run(initComponent);
} }
createFakeAsync(rootComponentType: Type): ComponentFixture<any> { createFakeAsync<T>(rootComponentType: ConcreteType<T>): ComponentFixture<T> {
let result: any /** TODO #9100 */; let result: any /** TODO #9100 */;
let error: any /** TODO #9100 */; let error: any /** TODO #9100 */;
PromiseWrapper.then( PromiseWrapper.then(
@ -144,15 +146,16 @@ export class TestComponentBuilder {
return result; return result;
} }
/** createSync<T>(rootComponentType: ConcreteType<T>): ComponentFixture<T> {
* @deprecated createSync will be replaced with the ability to precompile components from within
* the test.
*/
createSync<C>(componentFactory: ComponentFactory<C>): ComponentFixture<C> {
let noNgZone = IS_DART || this._injector.get(ComponentFixtureNoNgZone, false); let noNgZone = IS_DART || this._injector.get(ComponentFixtureNoNgZone, false);
let ngZone: NgZone = noNgZone ? null : this._injector.get(NgZone, null); let ngZone: NgZone = noNgZone ? null : this._injector.get(NgZone, null);
let compiler: Compiler = this._injector.get(Compiler);
let initComponent = () => {
return this.createFromFactory(
ngZone, this._injector.get(Compiler).compileComponentSync(rootComponentType));
};
let initComponent = () => this.createFromFactory(ngZone, componentFactory);
return ngZone == null ? initComponent() : ngZone.run(initComponent); return ngZone == null ? initComponent() : ngZone.run(initComponent);
} }
} }

View File

@ -77,9 +77,7 @@ export interface Type extends Function {}
/** /**
* Runtime representation of a type that is constructable (non-abstract). * Runtime representation of a type that is constructable (non-abstract).
*/ */
export interface ConcreteType extends Type { new (...args: any[] /** TODO #9100 */): any; } export interface ConcreteType<T> extends Type { new (...args: any[]): T; }
export interface ClassWithConstructor<T> { new (...args: any[]): T; }
export function getTypeNameForDebugging(type: Type): string { export function getTypeNameForDebugging(type: Type): string {
if (type['name']) { if (type['name']) {

View File

@ -310,21 +310,24 @@ export declare class CompilerConfig {
}); });
} }
export declare class CompileTemplateMetadata { export declare class CompileTemplateMetadata implements CompileStylesheetMetadata {
animations: CompileAnimationEntryMetadata[]; animations: CompileAnimationEntryMetadata[];
encapsulation: ViewEncapsulation; encapsulation: ViewEncapsulation;
externalStylesheets: CompileStylesheetMetadata[];
interpolation: [string, string]; interpolation: [string, string];
moduleUrl: string;
ngContentSelectors: string[]; ngContentSelectors: string[];
styleUrls: string[]; styleUrls: string[];
styles: string[]; styles: string[];
template: string; template: string;
templateUrl: string; templateUrl: string;
constructor({encapsulation, template, templateUrl, styles, styleUrls, animations, ngContentSelectors, interpolation}?: { constructor({encapsulation, template, templateUrl, styles, styleUrls, externalStylesheets, animations, ngContentSelectors, interpolation}?: {
encapsulation?: ViewEncapsulation; encapsulation?: ViewEncapsulation;
template?: string; template?: string;
templateUrl?: string; templateUrl?: string;
styles?: string[]; styles?: string[];
styleUrls?: string[]; styleUrls?: string[];
externalStylesheets?: CompileStylesheetMetadata[];
ngContentSelectors?: string[]; ngContentSelectors?: string[];
animations?: CompileAnimationEntryMetadata[]; animations?: CompileAnimationEntryMetadata[];
interpolation?: [string, string]; interpolation?: [string, string];
@ -460,9 +463,8 @@ export declare class NormalizedComponentWithViewDirectives {
} }
export declare class OfflineCompiler { export declare class OfflineCompiler {
constructor(_directiveNormalizer: DirectiveNormalizer, _templateParser: TemplateParser, _styleCompiler: StyleCompiler, _viewCompiler: ViewCompiler, _outputEmitter: OutputEmitter, _xhr: XHR); constructor(_directiveNormalizer: DirectiveNormalizer, _templateParser: TemplateParser, _styleCompiler: StyleCompiler, _viewCompiler: ViewCompiler, _outputEmitter: OutputEmitter);
compileTemplates(components: NormalizedComponentWithViewDirectives[]): SourceModule; compileTemplates(components: NormalizedComponentWithViewDirectives[]): SourceModule[];
loadAndCompileStylesheet(stylesheetUrl: string, shim: boolean, suffix: string): Promise<StyleSheetSourceWithImports>;
normalizeDirectiveMetadata(directive: CompileDirectiveMetadata): Promise<CompileDirectiveMetadata>; normalizeDirectiveMetadata(directive: CompileDirectiveMetadata): Promise<CompileDirectiveMetadata>;
} }
@ -515,9 +517,12 @@ export declare abstract class RenderTypes {
renderer: CompileIdentifierMetadata; renderer: CompileIdentifierMetadata;
} }
export declare class RuntimeCompiler implements ComponentResolver { export declare class RuntimeCompiler implements ComponentResolver, Compiler {
constructor(_metadataResolver: CompileMetadataResolver, _templateNormalizer: DirectiveNormalizer, _templateParser: TemplateParser, _styleCompiler: StyleCompiler, _viewCompiler: ViewCompiler, _xhr: XHR, _genConfig: CompilerConfig); constructor(_metadataResolver: CompileMetadataResolver, _templateNormalizer: DirectiveNormalizer, _templateParser: TemplateParser, _styleCompiler: StyleCompiler, _viewCompiler: ViewCompiler, _genConfig: CompilerConfig);
clearCache(): void; clearCache(): void;
clearCacheFor(compType: Type): void;
compileComponentAsync<T>(compType: ConcreteType<T>): Promise<ComponentFactory<T>>;
compileComponentSync<T>(compType: ConcreteType<T>): ComponentFactory<T>;
resolveComponent(component: Type | string): Promise<ComponentFactory<any>>; resolveComponent(component: Type | string): Promise<ComponentFactory<any>>;
} }

View File

@ -1,4 +1,5 @@
export declare class MockDirectiveResolver extends DirectiveResolver { export declare class MockDirectiveResolver extends DirectiveResolver {
constructor(_injector: Injector);
resolve(type: Type): DirectiveMetadata; resolve(type: Type): DirectiveMetadata;
setProvidersOverride(type: Type, providers: any[]): void; setProvidersOverride(type: Type, providers: any[]): void;
setViewProvidersOverride(type: Type, viewProviders: any[]): void; setViewProvidersOverride(type: Type, viewProviders: any[]): void;
@ -22,7 +23,7 @@ export declare class MockSchemaRegistry implements ElementSchemaRegistry {
} }
export declare class MockViewResolver extends ViewResolver { export declare class MockViewResolver extends ViewResolver {
constructor(); constructor(_injector: Injector);
overrideViewDirective(component: Type, from: Type, to: Type): void; overrideViewDirective(component: Type, from: Type, to: Type): void;
resolve(component: Type): ViewMetadata; resolve(component: Type): ViewMetadata;
setAnimations(component: Type, animations: AnimationEntryMetadata[]): void; setAnimations(component: Type, animations: AnimationEntryMetadata[]): void;
@ -32,7 +33,8 @@ export declare class MockViewResolver extends ViewResolver {
export declare class OverridingTestComponentBuilder extends TestComponentBuilder { export declare class OverridingTestComponentBuilder extends TestComponentBuilder {
constructor(injector: Injector); constructor(injector: Injector);
createAsync(rootComponentType: Type): Promise<ComponentFixture<any>>; createAsync<T>(rootComponentType: ConcreteType<T>): Promise<ComponentFixture<T>>;
createSync<T>(rootComponentType: ConcreteType<T>): ComponentFixture<T>;
overrideAnimations(componentType: Type, animations: AnimationEntryMetadata[]): TestComponentBuilder; overrideAnimations(componentType: Type, animations: AnimationEntryMetadata[]): TestComponentBuilder;
overrideDirective(componentType: Type, from: Type, to: Type): OverridingTestComponentBuilder; overrideDirective(componentType: Type, from: Type, to: Type): OverridingTestComponentBuilder;
overrideProviders(type: Type, providers: any[]): OverridingTestComponentBuilder; overrideProviders(type: Type, providers: any[]): OverridingTestComponentBuilder;

View File

@ -207,7 +207,7 @@ export declare abstract class ChangeDetectorRef {
} }
/** @stable */ /** @stable */
export declare function Class(clsDef: ClassDefinition): ConcreteType; export declare function Class(clsDef: ClassDefinition): ConcreteType<any>;
/** @stable */ /** @stable */
export interface ClassDefinition { export interface ClassDefinition {
@ -226,6 +226,14 @@ export declare class CollectionChangeRecord {
toString(): string; toString(): string;
} }
/** @stable */
export declare class Compiler {
clearCache(): void;
clearCacheFor(compType: Type): void;
compileComponentAsync<T>(component: ConcreteType<T>): Promise<ComponentFactory<T>>;
compileComponentSync<T>(component: ConcreteType<T>): ComponentFactory<T>;
}
/** @stable */ /** @stable */
export declare var Component: ComponentMetadataFactory; export declare var Component: ComponentMetadataFactory;
@ -254,7 +262,7 @@ export declare class ComponentFactory<C> {
/** @stable */ /** @stable */
export declare abstract class ComponentFactoryResolver { export declare abstract class ComponentFactoryResolver {
abstract resolveComponentFactory<T>(component: ClassWithConstructor<T>): ComponentFactory<T>; abstract resolveComponentFactory<T>(component: ConcreteType<T>): ComponentFactory<T>;
static NULL: ComponentFactoryResolver; static NULL: ComponentFactoryResolver;
} }
@ -1267,7 +1275,7 @@ export interface TypeDecorator {
annotations: any[]; annotations: any[];
(target: Object, propertyKey?: string | symbol, parameterIndex?: number): void; (target: Object, propertyKey?: string | symbol, parameterIndex?: number): void;
<T extends Type>(type: T): T; <T extends Type>(type: T): T;
Class(obj: ClassDefinition): ConcreteType; Class(obj: ClassDefinition): ConcreteType<any>;
} }
/** @stable */ /** @stable */

View File

@ -89,10 +89,10 @@ export declare function setBaseTestProviders(platformProviders: Array<Type | Pro
export declare class TestComponentBuilder { export declare class TestComponentBuilder {
protected _injector: Injector; protected _injector: Injector;
constructor(_injector: Injector); constructor(_injector: Injector);
createAsync(rootComponentType: Type): Promise<ComponentFixture<any>>; createAsync<T>(rootComponentType: ConcreteType<T>): Promise<ComponentFixture<T>>;
createFakeAsync(rootComponentType: Type): ComponentFixture<any>; createFakeAsync<T>(rootComponentType: ConcreteType<T>): ComponentFixture<T>;
protected createFromFactory<C>(ngZone: NgZone, componentFactory: ComponentFactory<C>): ComponentFixture<C>; protected createFromFactory<C>(ngZone: NgZone, componentFactory: ComponentFactory<C>): ComponentFixture<C>;
/** @deprecated */ createSync<C>(componentFactory: ComponentFactory<C>): ComponentFixture<C>; createSync<T>(rootComponentType: ConcreteType<T>): ComponentFixture<T>;
overrideAnimations(componentType: Type, animations: AnimationEntryMetadata[]): TestComponentBuilder; overrideAnimations(componentType: Type, animations: AnimationEntryMetadata[]): TestComponentBuilder;
overrideDirective(componentType: Type, from: Type, to: Type): TestComponentBuilder; overrideDirective(componentType: Type, from: Type, to: Type): TestComponentBuilder;
overrideProviders(type: Type, providers: any[]): TestComponentBuilder; overrideProviders(type: Type, providers: any[]): TestComponentBuilder;