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:
parent
24eb8389d2
commit
bf598d6b8b
|
@ -101,44 +101,23 @@ export class CodeGenerator {
|
|||
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> {
|
||||
let stylesheetPromises: Promise<any>[] = [];
|
||||
const generateOneFile = (absSourcePath: string) =>
|
||||
Promise.all(this.readComponents(absSourcePath))
|
||||
.then((metadatas: compiler.CompileDirectiveMetadata[]) => {
|
||||
if (!metadatas || !metadatas.length) {
|
||||
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);
|
||||
})
|
||||
.then(generated => {
|
||||
if (generated) {
|
||||
const sourceFile = this.program.getSourceFile(absSourcePath);
|
||||
const emitPath = this.calculateEmitPath(generated.moduleUrl);
|
||||
this.host.writeFile(
|
||||
emitPath, PREAMBLE + generated.source, false, () => {}, [sourceFile]);
|
||||
.then(generatedModules => {
|
||||
if (generatedModules) {
|
||||
generatedModules.forEach((generatedModule) => {
|
||||
const sourceFile = this.program.getSourceFile(absSourcePath);
|
||||
const emitPath = this.calculateEmitPath(generatedModule.moduleUrl);
|
||||
this.host.writeFile(
|
||||
emitPath, PREAMBLE + generatedModule.source, false, () => {}, [sourceFile]);
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((e) => { console.error(e.stack); });
|
||||
|
@ -146,7 +125,7 @@ export class CodeGenerator {
|
|||
.map(sf => sf.fileName)
|
||||
.filter(f => !GENERATED_FILES.test(f))
|
||||
.map(generateOneFile);
|
||||
return Promise.all(stylesheetPromises.concat(compPromises));
|
||||
return Promise.all(compPromises);
|
||||
}
|
||||
|
||||
static create(
|
||||
|
@ -173,7 +152,7 @@ export class CodeGenerator {
|
|||
/*console*/ null, []);
|
||||
const offlineCompiler = new compiler.OfflineCompiler(
|
||||
normalizer, tmplParser, new StyleCompiler(urlResolver), new ViewCompiler(config),
|
||||
new TypeScriptEmitter(reflectorHost), xhr);
|
||||
new TypeScriptEmitter(reflectorHost));
|
||||
const resolver = new CompileMetadataResolver(
|
||||
new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector),
|
||||
new compiler.ViewResolver(staticReflector), config, staticReflector);
|
||||
|
|
|
@ -162,7 +162,7 @@ class Extractor {
|
|||
/*console*/ null, []);
|
||||
const offlineCompiler = new compiler.OfflineCompiler(
|
||||
normalizer, tmplParser, new StyleCompiler(urlResolver), new ViewCompiler(config),
|
||||
new TypeScriptEmitter(reflectorHost), xhr);
|
||||
new TypeScriptEmitter(reflectorHost));
|
||||
const resolver = new CompileMetadataResolver(
|
||||
new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector),
|
||||
new compiler.ViewResolver(staticReflector), config, staticReflector);
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
@ -609,17 +634,19 @@ export class CompileTemplateMetadata {
|
|||
templateUrl: string;
|
||||
styles: string[];
|
||||
styleUrls: string[];
|
||||
externalStylesheets: CompileStylesheetMetadata[];
|
||||
animations: CompileAnimationEntryMetadata[];
|
||||
ngContentSelectors: string[];
|
||||
interpolation: [string, string];
|
||||
constructor(
|
||||
{encapsulation, template, templateUrl, styles, styleUrls, animations, ngContentSelectors,
|
||||
interpolation}: {
|
||||
{encapsulation, template, templateUrl, styles, styleUrls, externalStylesheets, animations,
|
||||
ngContentSelectors, interpolation}: {
|
||||
encapsulation?: ViewEncapsulation,
|
||||
template?: string,
|
||||
templateUrl?: string,
|
||||
styles?: string[],
|
||||
styleUrls?: string[],
|
||||
externalStylesheets?: CompileStylesheetMetadata[],
|
||||
ngContentSelectors?: string[],
|
||||
animations?: CompileAnimationEntryMetadata[],
|
||||
interpolation?: [string, string]
|
||||
|
@ -627,8 +654,9 @@ export class CompileTemplateMetadata {
|
|||
this.encapsulation = encapsulation;
|
||||
this.template = template;
|
||||
this.templateUrl = templateUrl;
|
||||
this.styles = isPresent(styles) ? styles : [];
|
||||
this.styleUrls = isPresent(styleUrls) ? styleUrls : [];
|
||||
this.styles = _normalizeArray(styles);
|
||||
this.styleUrls = _normalizeArray(styleUrls);
|
||||
this.externalStylesheets = _normalizeArray(externalStylesheets);
|
||||
this.animations = isPresent(animations) ? ListWrapper.flatten(animations) : [];
|
||||
this.ngContentSelectors = isPresent(ngContentSelectors) ? ngContentSelectors : [];
|
||||
if (isPresent(interpolation) && interpolation.length != 2) {
|
||||
|
@ -648,6 +676,8 @@ export class CompileTemplateMetadata {
|
|||
templateUrl: data['templateUrl'],
|
||||
styles: data['styles'],
|
||||
styleUrls: data['styleUrls'],
|
||||
externalStylesheets:
|
||||
_arrayFromJson(data['externalStylesheets'], CompileStylesheetMetadata.fromJson),
|
||||
animations: animations,
|
||||
ngContentSelectors: data['ngContentSelectors'],
|
||||
interpolation: data['interpolation']
|
||||
|
@ -662,6 +692,7 @@ export class CompileTemplateMetadata {
|
|||
'templateUrl': this.templateUrl,
|
||||
'styles': this.styles,
|
||||
'styleUrls': this.styleUrls,
|
||||
'externalStylesheets': _objToJson(this.externalStylesheets),
|
||||
'animations': _objToJson(this.animations),
|
||||
'ngContentSelectors': this.ngContentSelectors,
|
||||
'interpolation': this.interpolation
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* 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 {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()},
|
||||
RuntimeCompiler,
|
||||
/*@ts2dart_Provider*/ {provide: ComponentResolver, useExisting: RuntimeCompiler},
|
||||
/*@ts2dart_Provider*/ {provide: Compiler, useExisting: RuntimeCompiler},
|
||||
DomElementSchemaRegistry,
|
||||
/*@ts2dart_Provider*/ {provide: ElementSchemaRegistry, useExisting: DomElementSchemaRegistry},
|
||||
UrlResolver, ViewResolver, DirectiveResolver, PipeResolver
|
||||
|
|
|
@ -12,65 +12,92 @@ import {PromiseWrapper} from '../src/facade/async';
|
|||
import {BaseException} from '../src/facade/exceptions';
|
||||
import {isBlank, isPresent} from '../src/facade/lang';
|
||||
|
||||
import {CompileTypeMetadata, CompileDirectiveMetadata, CompileTemplateMetadata,} 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 {CompileDirectiveMetadata, CompileStylesheetMetadata, CompileTemplateMetadata, CompileTypeMetadata} from './compile_metadata';
|
||||
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()
|
||||
export class DirectiveNormalizer {
|
||||
private _xhrCache = new Map<string, Promise<string>>();
|
||||
|
||||
constructor(
|
||||
private _xhr: XHR, private _urlResolver: UrlResolver, private _htmlParser: HtmlParser,
|
||||
private _config: CompilerConfig) {}
|
||||
|
||||
normalizeDirective(directive: CompileDirectiveMetadata): Promise<CompileDirectiveMetadata> {
|
||||
if (!directive.isComponent) {
|
||||
// For non components there is nothing to be normalized yet.
|
||||
return PromiseWrapper.resolve(directive);
|
||||
clearCache() { this._xhrCache.clear(); }
|
||||
|
||||
clearCacheFor(normalizedDirective: CompileDirectiveMetadata) {
|
||||
if (!normalizedDirective.isComponent) {
|
||||
return;
|
||||
}
|
||||
return this.normalizeTemplate(directive.type, directive.template)
|
||||
.then((normalizedTemplate: CompileTemplateMetadata) => 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: normalizedTemplate
|
||||
}));
|
||||
this._xhrCache.delete(normalizedDirective.template.templateUrl);
|
||||
normalizedDirective.template.externalStylesheets.forEach(
|
||||
(stylesheet) => { this._xhrCache.delete(stylesheet.moduleUrl); });
|
||||
}
|
||||
|
||||
normalizeTemplate(directiveType: CompileTypeMetadata, template: CompileTemplateMetadata):
|
||||
Promise<CompileTemplateMetadata> {
|
||||
if (isPresent(template.template)) {
|
||||
return PromiseWrapper.resolve(this.normalizeLoadedTemplate(
|
||||
directiveType, template, template.template, directiveType.moduleUrl));
|
||||
} 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}`);
|
||||
private _fetch(url: string): Promise<string> {
|
||||
var result = this._xhrCache.get(url);
|
||||
if (!result) {
|
||||
result = this._xhr.get(url);
|
||||
this._xhrCache.set(url, result);
|
||||
}
|
||||
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(
|
||||
|
@ -81,42 +108,87 @@ export class DirectiveNormalizer {
|
|||
var errorString = rootNodesAndErrors.errors.join('\n');
|
||||
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();
|
||||
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 =
|
||||
visitor.styleUrls.filter(isStyleUrlResolvable)
|
||||
.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 allStyles = templateMetadataStyles.styles.concat(templateStyles.styles);
|
||||
var allStyleUrls = templateMetadataStyles.styleUrls.concat(templateStyles.styleUrls);
|
||||
|
||||
var encapsulation = templateMeta.encapsulation;
|
||||
if (isBlank(encapsulation)) {
|
||||
encapsulation = this._config.defaultEncapsulation;
|
||||
}
|
||||
if (encapsulation === ViewEncapsulation.Emulated && allResolvedStyles.length === 0 &&
|
||||
allStyleAbsUrls.length === 0) {
|
||||
if (encapsulation === ViewEncapsulation.Emulated && allStyles.length === 0 &&
|
||||
allStyleUrls.length === 0) {
|
||||
encapsulation = ViewEncapsulation.None;
|
||||
}
|
||||
return new CompileTemplateMetadata({
|
||||
encapsulation: encapsulation,
|
||||
template: template,
|
||||
templateUrl: templateAbsUrl,
|
||||
styles: allResolvedStyles,
|
||||
styleUrls: allStyleAbsUrls,
|
||||
styles: allStyles,
|
||||
styleUrls: allStyleUrls,
|
||||
externalStylesheets: templateMeta.externalStylesheets,
|
||||
ngContentSelectors: visitor.ngContentSelectors,
|
||||
animations: templateMeta.animations,
|
||||
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 {
|
||||
|
@ -166,3 +238,27 @@ class TemplatePreparseVisitor implements HtmlAstVisitor {
|
|||
|
||||
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
|
||||
});
|
||||
}
|
||||
|
|
|
@ -49,6 +49,16 @@ export class CompileMetadataResolver {
|
|||
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 {
|
||||
var defs = entry.definitions.map(def => this.getAnimationStateMetadata(def));
|
||||
return new cpl.CompileAnimationEntryMetadata(entry.name, defs);
|
||||
|
@ -101,7 +111,6 @@ export class CompileMetadataResolver {
|
|||
var moduleUrl = staticTypeModuleUrl(directiveType);
|
||||
var precompileTypes: cpl.CompileTypeMetadata[] = [];
|
||||
if (dirMeta instanceof ComponentMetadata) {
|
||||
assertArrayOfStrings('styles', dirMeta.styles);
|
||||
var cmpMeta = <ComponentMetadata>dirMeta;
|
||||
var viewMeta = this._viewResolver.resolve(directiveType);
|
||||
assertArrayOfStrings('styles', viewMeta.styles);
|
||||
|
@ -109,6 +118,8 @@ export class CompileMetadataResolver {
|
|||
var animations = isPresent(viewMeta.animations) ?
|
||||
viewMeta.animations.map(e => this.getAnimationEntryMetadata(e)) :
|
||||
null;
|
||||
assertArrayOfStrings('styles', viewMeta.styles);
|
||||
assertArrayOfStrings('styleUrls', viewMeta.styleUrls);
|
||||
|
||||
templateMeta = new cpl.CompileTemplateMetadata({
|
||||
encapsulation: viewMeta.encapsulation,
|
||||
|
|
|
@ -14,7 +14,7 @@ import {ListWrapper} from './facade/collection';
|
|||
import {BaseException} from './facade/exceptions';
|
||||
import {OutputEmitter} from './output/abstract_emitter';
|
||||
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 {assetUrl} from './util';
|
||||
import {ComponentFactoryDependency, ViewCompileResult, ViewCompiler, ViewFactoryDependency} from './view_compiler/view_compiler';
|
||||
|
@ -44,29 +44,38 @@ export class OfflineCompiler {
|
|||
constructor(
|
||||
private _directiveNormalizer: DirectiveNormalizer, private _templateParser: TemplateParser,
|
||||
private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler,
|
||||
private _outputEmitter: OutputEmitter, private _xhr: XHR) {}
|
||||
private _outputEmitter: OutputEmitter) {}
|
||||
|
||||
normalizeDirectiveMetadata(directive: 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) {
|
||||
throw new BaseException('No components given');
|
||||
}
|
||||
var statements: o.DeclareVarStmt[] = [];
|
||||
var exportedVars: string[] = [];
|
||||
var moduleUrl = _ngfactoryModuleUrl(components[0].component.type);
|
||||
var outputSourceModules: SourceModule[] = [];
|
||||
components.forEach(componentWithDirs => {
|
||||
var compMeta = <CompileDirectiveMetadata>componentWithDirs.component;
|
||||
_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(
|
||||
compMeta, componentWithDirs.directives, componentWithDirs.pipes, statements);
|
||||
compMeta, componentWithDirs.directives, componentWithDirs.pipes,
|
||||
stylesCompileResults.componentStylesheet, fileSuffix, statements);
|
||||
exportedVars.push(compViewFactoryVar);
|
||||
|
||||
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);
|
||||
statements.push(
|
||||
o.variable(compFactoryVar)
|
||||
|
@ -82,43 +91,32 @@ export class OfflineCompiler {
|
|||
.toDeclStmt(null, [o.StmtModifier.Final]));
|
||||
exportedVars.push(compFactoryVar);
|
||||
});
|
||||
return this._codegenSourceModule(moduleUrl, statements, exportedVars);
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
outputSourceModules.unshift(this._codegenSourceModule(moduleUrl, statements, exportedVars));
|
||||
return outputSourceModules;
|
||||
}
|
||||
|
||||
private _compileComponent(
|
||||
compMeta: CompileDirectiveMetadata, directives: CompileDirectiveMetadata[],
|
||||
pipes: CompilePipeMetadata[], targetStatements: o.Statement[]): string {
|
||||
var styleResult = this._styleCompiler.compileComponent(compMeta);
|
||||
pipes: CompilePipeMetadata[], componentStyles: CompiledStylesheet, fileSuffix: string,
|
||||
targetStatements: o.Statement[]): string {
|
||||
var parsedTemplate = this._templateParser.parse(
|
||||
compMeta, compMeta.template.template, directives, pipes, compMeta.type.name);
|
||||
var viewResult = this._viewCompiler.compileComponent(
|
||||
compMeta, parsedTemplate, o.variable(styleResult.stylesVar), pipes);
|
||||
ListWrapper.addAll(
|
||||
targetStatements, _resolveStyleStatements(compMeta.type.moduleUrl, styleResult));
|
||||
var stylesExpr = componentStyles ? o.variable(componentStyles.stylesVar) : o.literalArr([]);
|
||||
var viewResult =
|
||||
this._viewCompiler.compileComponent(compMeta, parsedTemplate, stylesExpr, pipes);
|
||||
if (componentStyles) {
|
||||
ListWrapper.addAll(targetStatements, _resolveStyleStatements(componentStyles, fileSuffix));
|
||||
}
|
||||
ListWrapper.addAll(targetStatements, _resolveViewStatements(viewResult));
|
||||
return viewResult.viewFactoryVar;
|
||||
}
|
||||
|
||||
private _codgenStyles(
|
||||
inputUrl: string, shim: boolean, suffix: string,
|
||||
stylesCompileResult: StylesCompileResult): SourceModule {
|
||||
private _codgenStyles(stylesCompileResult: CompiledStylesheet, fileSuffix: string): SourceModule {
|
||||
_resolveStyleStatements(stylesCompileResult, fileSuffix);
|
||||
return this._codegenSourceModule(
|
||||
_stylesModuleUrl(inputUrl, shim, suffix), stylesCompileResult.statements,
|
||||
[stylesCompileResult.stylesVar]);
|
||||
_stylesModuleUrl(
|
||||
stylesCompileResult.meta.moduleUrl, stylesCompileResult.isShimmed, fileSuffix),
|
||||
stylesCompileResult.statements, [stylesCompileResult.stylesVar]);
|
||||
}
|
||||
|
||||
private _codegenSourceModule(
|
||||
|
@ -131,7 +129,7 @@ export class OfflineCompiler {
|
|||
function _resolveViewStatements(compileResult: ViewCompileResult): o.Statement[] {
|
||||
compileResult.dependencies.forEach((dep) => {
|
||||
if (dep instanceof ViewFactoryDependency) {
|
||||
dep.placeholder.moduleUrl = _ngfactoryModuleUrl(dep.comp.type);
|
||||
dep.placeholder.moduleUrl = _ngfactoryModuleUrl(dep.comp);
|
||||
} else if (dep instanceof ComponentFactoryDependency) {
|
||||
dep.placeholder.name = _componentFactoryName(dep.comp);
|
||||
dep.placeholder.moduleUrl = _ngfactoryModuleUrl(dep.comp);
|
||||
|
@ -142,17 +140,15 @@ function _resolveViewStatements(compileResult: ViewCompileResult): o.Statement[]
|
|||
|
||||
|
||||
function _resolveStyleStatements(
|
||||
containingModuleUrl: string, compileResult: StylesCompileResult): o.Statement[] {
|
||||
var containingSuffix = _splitSuffix(containingModuleUrl)[1];
|
||||
compileResult: CompiledStylesheet, fileSuffix: string): o.Statement[] {
|
||||
compileResult.dependencies.forEach((dep) => {
|
||||
dep.valuePlaceholder.moduleUrl =
|
||||
_stylesModuleUrl(dep.moduleUrl, dep.isShimmed, containingSuffix);
|
||||
dep.valuePlaceholder.moduleUrl = _stylesModuleUrl(dep.moduleUrl, dep.isShimmed, fileSuffix);
|
||||
});
|
||||
return compileResult.statements;
|
||||
}
|
||||
|
||||
function _ngfactoryModuleUrl(comp: CompileIdentifierMetadata): string {
|
||||
var urlWithSuffix = _splitSuffix(comp.moduleUrl);
|
||||
var urlWithSuffix = _splitLastSuffix(comp.moduleUrl);
|
||||
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('.');
|
||||
if (lastDot !== -1) {
|
||||
return [path.substring(0, lastDot), path.substring(lastDot)];
|
||||
|
|
|
@ -6,26 +6,25 @@
|
|||
* 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 {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 {PromiseWrapper} from '../src/facade/async';
|
||||
import {createHostComponentMeta, CompileDirectiveMetadata, CompilePipeMetadata, CompileIdentifierMetadata} from './compile_metadata';
|
||||
import {TemplateAst,} from './template_ast';
|
||||
import {StyleCompiler, StylesCompileDependency, StylesCompileResult} from './style_compiler';
|
||||
import {ViewCompiler, ViewFactoryDependency, ComponentFactoryDependency} from './view_compiler/view_compiler';
|
||||
import {StyleCompiler, StylesCompileDependency, CompiledStylesheet} from './style_compiler';
|
||||
import {ViewCompiler, ViewCompileResult, ViewFactoryDependency, ComponentFactoryDependency} from './view_compiler/view_compiler';
|
||||
import {TemplateParser} from './template_parser';
|
||||
import {DirectiveNormalizer} from './directive_normalizer';
|
||||
import {DirectiveNormalizer, NormalizeDirectiveResult} from './directive_normalizer';
|
||||
import {CompileMetadataResolver} from './metadata_resolver';
|
||||
import {CompilerConfig} from './config';
|
||||
import * as ir from './output/output_ast';
|
||||
import {jitStatements} from './output/output_jit';
|
||||
import {interpretStatements} from './output/output_interpreter';
|
||||
import {InterpretiveAppViewInstanceFactory} from './output/interpretive_view';
|
||||
import {XHR} from './xhr';
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@Injectable()
|
||||
export class RuntimeCompiler implements ComponentResolver {
|
||||
private _styleCache: Map<string, Promise<string>> = new Map<string, Promise<string>>();
|
||||
private _hostCacheKeys = new Map<Type, any>();
|
||||
export class RuntimeCompiler implements ComponentResolver, Compiler {
|
||||
private _compiledTemplateCache = new Map<any, CompiledTemplate>();
|
||||
private _compiledHostTemplateCache = new Map<any, CompiledTemplate>();
|
||||
|
||||
constructor(
|
||||
private _metadataResolver: CompileMetadataResolver,
|
||||
private _templateNormalizer: DirectiveNormalizer, private _templateParser: TemplateParser,
|
||||
private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler, private _xhr: XHR,
|
||||
private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler,
|
||||
private _genConfig: CompilerConfig) {}
|
||||
|
||||
resolveComponent(component: Type|string): Promise<ComponentFactory<any>> {
|
||||
|
@ -49,175 +47,215 @@ export class RuntimeCompiler implements ComponentResolver {
|
|||
return PromiseWrapper.reject(
|
||||
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 {
|
||||
this._styleCache.clear();
|
||||
this._metadataResolver.clearCache();
|
||||
this._compiledTemplateCache.clear();
|
||||
this._hostCacheKeys.clear();
|
||||
this._compiledHostTemplateCache.clear();
|
||||
this._templateNormalizer.clearCache();
|
||||
}
|
||||
|
||||
private _loadAndCompileHostComponent(componentType: Type): CompileHostTemplate {
|
||||
var compMeta: CompileDirectiveMetadata =
|
||||
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);
|
||||
private _getCompiledHostTemplate(type: Type): CompiledTemplate {
|
||||
var compiledTemplate = this._compiledHostTemplateCache.get(type);
|
||||
if (isBlank(compiledTemplate)) {
|
||||
let done =
|
||||
PromiseWrapper
|
||||
.all([<any>this._compileComponentStyles(compMeta)].concat(viewDirectives.map(
|
||||
dirMeta => this._templateNormalizer.normalizeDirective(dirMeta))))
|
||||
.then((stylesAndNormalizedViewDirMetas: any[]) => {
|
||||
var normalizedViewDirMetas = stylesAndNormalizedViewDirMetas.slice(1);
|
||||
var styles = stylesAndNormalizedViewDirMetas[0];
|
||||
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);
|
||||
var compMeta = this._metadataResolver.getDirectiveMetadata(type);
|
||||
assertComponent(compMeta);
|
||||
var hostMeta = createHostComponentMeta(compMeta.type, compMeta.selector);
|
||||
compiledTemplate = new CompiledTemplate(
|
||||
true, compMeta.selector, compMeta.type, [], [type], [], [],
|
||||
this._templateNormalizer.normalizeDirective(hostMeta));
|
||||
this._compiledHostTemplateCache.set(type, compiledTemplate);
|
||||
}
|
||||
return compiledTemplate;
|
||||
}
|
||||
|
||||
private _compileComponent(
|
||||
compMeta: CompileDirectiveMetadata, parsedTemplate: TemplateAst[], styles: string[],
|
||||
pipes: CompilePipeMetadata[], compilingComponentsPath: any[],
|
||||
childPromises: Promise<any>[]): Function {
|
||||
var compileResult = this._viewCompiler.compileComponent(
|
||||
compMeta, parsedTemplate,
|
||||
new ir.ExternalExpr(new CompileIdentifierMetadata({runtime: styles})), pipes);
|
||||
compileResult.dependencies.forEach((dep) => {
|
||||
if (dep instanceof ViewFactoryDependency) {
|
||||
let childCompilingComponentsPath = ListWrapper.clone(compilingComponentsPath);
|
||||
let childCacheKey = dep.comp.type.runtime;
|
||||
let childViewDirectives: CompileDirectiveMetadata[] =
|
||||
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);
|
||||
private _getCompiledTemplate(type: Type): CompiledTemplate {
|
||||
var compiledTemplate = this._compiledTemplateCache.get(type);
|
||||
if (isBlank(compiledTemplate)) {
|
||||
var compMeta = this._metadataResolver.getDirectiveMetadata(type);
|
||||
assertComponent(compMeta);
|
||||
var viewDirectives: CompileDirectiveMetadata[] = [];
|
||||
var viewComponentTypes: Type[] = [];
|
||||
this._metadataResolver.getViewDirectivesMetadata(type).forEach(dirOrComp => {
|
||||
if (dirOrComp.isComponent) {
|
||||
viewComponentTypes.push(dirOrComp.type.runtime);
|
||||
} else {
|
||||
viewDirectives.push(dirOrComp);
|
||||
}
|
||||
});
|
||||
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) {
|
||||
let childComp = this._loadAndCompileHostComponent(dep.comp.runtime);
|
||||
dep.placeholder.runtime = childComp.componentFactory;
|
||||
depTemplate = this._getCompiledHostTemplate(dep.comp.runtime);
|
||||
dep.placeholder.runtime = depTemplate.proxyComponentFactory;
|
||||
dep.placeholder.name = `compFactory_${dep.comp.name}`;
|
||||
childPromises.push(childComp.done);
|
||||
}
|
||||
return depTemplate;
|
||||
});
|
||||
var statements =
|
||||
stylesCompileResult.componentStylesheet.statements.concat(compileResult.statements);
|
||||
var factory: any;
|
||||
if (IS_DART || !this._genConfig.useJit) {
|
||||
factory = interpretStatements(
|
||||
compileResult.statements, compileResult.viewFactoryVar,
|
||||
new InterpretiveAppViewInstanceFactory());
|
||||
statements, compileResult.viewFactoryVar, new InterpretiveAppViewInstanceFactory());
|
||||
} else {
|
||||
factory = jitStatements(
|
||||
`${compMeta.type.name}.template.js`, compileResult.statements,
|
||||
compileResult.viewFactoryVar);
|
||||
`${template.compType.name}.template.js`, statements, compileResult.viewFactoryVar);
|
||||
}
|
||||
return factory;
|
||||
template.compiled(factory);
|
||||
}
|
||||
|
||||
private _compileComponentStyles(compMeta: CompileDirectiveMetadata): Promise<string[]> {
|
||||
var compileResult = this._styleCompiler.compileComponent(compMeta);
|
||||
return this._resolveStylesCompileResult(compMeta.type.name, compileResult);
|
||||
private _resolveStylesCompileResult(
|
||||
result: CompiledStylesheet, externalStylesheetsByModuleUrl: Map<string, CompiledStylesheet>) {
|
||||
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):
|
||||
Promise<string[]> {
|
||||
var promises = result.dependencies.map((dep) => this._loadStylesheetDep(dep));
|
||||
return PromiseWrapper.all(promises)
|
||||
.then((cssTexts) => {
|
||||
var nestedCompileResultPromises: Promise<string[]>[] = [];
|
||||
for (var i = 0; i < result.dependencies.length; i++) {
|
||||
var dep = result.dependencies[i];
|
||||
var cssText = cssTexts[i];
|
||||
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);
|
||||
private _resolveAndEvalStylesCompileResult(
|
||||
result: CompiledStylesheet,
|
||||
externalStylesheetsByModuleUrl: Map<string, CompiledStylesheet>): string[] {
|
||||
this._resolveStylesCompileResult(result, externalStylesheetsByModuleUrl);
|
||||
if (IS_DART || !this._genConfig.useJit) {
|
||||
return interpretStatements(
|
||||
result.statements, result.stylesVar, new InterpretiveAppViewInstanceFactory());
|
||||
} else {
|
||||
return jitStatements(`${result.meta.moduleUrl}.css.js`, result.statements, result.stylesVar);
|
||||
}
|
||||
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 {
|
||||
private _viewFactory: Function = null;
|
||||
proxyViewFactory: Function;
|
||||
constructor(public done: Promise<CompiledTemplate>) {
|
||||
this.proxyViewFactory =
|
||||
(viewUtils: any /** TODO #9100 */, childInjector: any /** TODO #9100 */,
|
||||
contextEl: any /** TODO #9100 */) =>
|
||||
this._viewFactory(viewUtils, childInjector, contextEl);
|
||||
proxyComponentFactory: ComponentFactory<any>;
|
||||
loading: Promise<any> = null;
|
||||
private _normalizedCompMeta: CompileDirectiveMetadata = null;
|
||||
isCompiled = false;
|
||||
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) {
|
||||
|
|
|
@ -10,7 +10,7 @@ import {Injectable, ViewEncapsulation} from '@angular/core';
|
|||
|
||||
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 {ShadowCss} from './shadow_css';
|
||||
import {extractStyleUrls} from './style_url_resolver';
|
||||
|
@ -27,9 +27,16 @@ export class StylesCompileDependency {
|
|||
}
|
||||
|
||||
export class StylesCompileResult {
|
||||
constructor(
|
||||
public componentStylesheet: CompiledStylesheet,
|
||||
public externalStylesheets: CompiledStylesheet[]) {}
|
||||
}
|
||||
|
||||
export class CompiledStylesheet {
|
||||
constructor(
|
||||
public statements: o.Statement[], public stylesVar: string,
|
||||
public dependencies: StylesCompileDependency[]) {}
|
||||
public dependencies: StylesCompileDependency[], public isShimmed: boolean,
|
||||
public meta: CompileStylesheetMetadata) {}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
|
@ -40,35 +47,41 @@ export class StyleCompiler {
|
|||
|
||||
compileComponent(comp: CompileDirectiveMetadata): StylesCompileResult {
|
||||
var shim = comp.template.encapsulation === ViewEncapsulation.Emulated;
|
||||
return this._compileStyles(
|
||||
getStylesVarName(comp), comp.template.styles, comp.template.styleUrls, shim);
|
||||
}
|
||||
|
||||
compileStylesheet(stylesheetUrl: string, cssText: string, isShimmed: boolean):
|
||||
StylesCompileResult {
|
||||
var styleWithImports = extractStyleUrls(this._urlResolver, stylesheetUrl, cssText);
|
||||
return this._compileStyles(
|
||||
getStylesVarName(null), [styleWithImports.style], styleWithImports.styleUrls, isShimmed);
|
||||
var externalStylesheets: CompiledStylesheet[] = [];
|
||||
var componentStylesheet: CompiledStylesheet = this._compileStyles(
|
||||
comp, new CompileStylesheetMetadata({
|
||||
styles: comp.template.styles,
|
||||
styleUrls: comp.template.styleUrls,
|
||||
moduleUrl: comp.type.moduleUrl
|
||||
}),
|
||||
true);
|
||||
comp.template.externalStylesheets.forEach((stylesheetMeta) => {
|
||||
var compiledStylesheet = this._compileStyles(comp, stylesheetMeta, false);
|
||||
externalStylesheets.push(compiledStylesheet);
|
||||
});
|
||||
return new StylesCompileResult(componentStylesheet, externalStylesheets);
|
||||
}
|
||||
|
||||
private _compileStyles(
|
||||
stylesVar: string, plainStyles: string[], absUrls: string[],
|
||||
shim: boolean): StylesCompileResult {
|
||||
comp: CompileDirectiveMetadata, stylesheet: CompileStylesheetMetadata,
|
||||
isComponentStylesheet: boolean): CompiledStylesheet {
|
||||
var shim = comp.template.encapsulation === ViewEncapsulation.Emulated;
|
||||
var styleExpressions =
|
||||
plainStyles.map(plainStyle => o.literal(this._shimIfNeeded(plainStyle, shim)));
|
||||
stylesheet.styles.map(plainStyle => o.literal(this._shimIfNeeded(plainStyle, shim)));
|
||||
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)});
|
||||
dependencies.push(new StylesCompileDependency(absUrls[i], shim, identifier));
|
||||
dependencies.push(new StylesCompileDependency(stylesheet.styleUrls[i], shim, identifier));
|
||||
styleExpressions.push(new o.ExternalExpr(identifier));
|
||||
}
|
||||
// styles variable contains plain strings and arrays of other styles arrays (recursive),
|
||||
// so we set its type to dynamic.
|
||||
var stylesVar = getStylesVarName(isComponentStylesheet ? comp : null);
|
||||
var stmt = o.variable(stylesVar)
|
||||
.set(o.literalArr(
|
||||
styleExpressions, new o.ArrayType(o.DYNAMIC_TYPE, [o.TypeModifier.Const])))
|
||||
.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 {
|
||||
|
@ -78,7 +91,7 @@ export class StyleCompiler {
|
|||
|
||||
function getStylesVarName(component: CompileDirectiveMetadata): string {
|
||||
var result = `styles`;
|
||||
if (isPresent(component)) {
|
||||
if (component) {
|
||||
result += `_${component.type.name}`;
|
||||
}
|
||||
return result;
|
||||
|
|
|
@ -32,7 +32,7 @@ var rootSelectorVar = o.variable('rootSelector');
|
|||
|
||||
export class ViewFactoryDependency {
|
||||
constructor(
|
||||
public comp: CompileDirectiveMetadata, public placeholder: CompileIdentifierMetadata) {}
|
||||
public comp: CompileIdentifierMetadata, public placeholder: CompileIdentifierMetadata) {}
|
||||
}
|
||||
|
||||
export class ComponentFactoryDependency {
|
||||
|
@ -208,7 +208,8 @@ class ViewBuilderVisitor implements TemplateAstVisitor {
|
|||
if (isPresent(component)) {
|
||||
let nestedComponentIdentifier =
|
||||
new CompileIdentifierMetadata({name: getViewFactoryName(component, 0)});
|
||||
this.targetDependencies.push(new ViewFactoryDependency(component, nestedComponentIdentifier));
|
||||
this.targetDependencies.push(
|
||||
new ViewFactoryDependency(component.type, nestedComponentIdentifier));
|
||||
let precompileComponentIdentifiers =
|
||||
component.precompile.map((precompileComp: CompileIdentifierMetadata) => {
|
||||
var id = new CompileIdentifierMetadata({name: precompileComp.name});
|
||||
|
|
|
@ -19,24 +19,9 @@ import {Map} from '../src/facade/collection';
|
|||
*/
|
||||
@Injectable()
|
||||
export class ViewResolver {
|
||||
/** @internal */
|
||||
_cache = new Map<Type, ViewMetadata>();
|
||||
|
||||
constructor(private _reflector: ReflectorReader = reflector) {}
|
||||
|
||||
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;
|
||||
|
||||
this._reflector.annotations(component).forEach(m => {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* 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 {DirectiveNormalizer} from '@angular/compiler/src/directive_normalizer';
|
||||
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 {AsyncTestCompleter} from '@angular/core/testing/testing_internal';
|
||||
|
||||
import {SpyXHR} from './spies';
|
||||
import {TEST_PROVIDERS} from './test_bindings';
|
||||
|
||||
export function main() {
|
||||
|
@ -30,176 +31,230 @@ export function main() {
|
|||
new CompileTypeMetadata({moduleUrl: 'http://some/module/a.js', name: 'SomeComp'});
|
||||
});
|
||||
|
||||
describe('loadTemplate', () => {
|
||||
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();
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
describe('normalizeDirective', () => {
|
||||
it('should throw if no template was specified',
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
expect(
|
||||
() => normalizer.normalizeTemplate(
|
||||
dirType,
|
||||
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []})))
|
||||
.toThrowError('No template specified for component SomeComp');
|
||||
expect(() => normalizer.normalizeDirective(new CompileDirectiveMetadata({
|
||||
type: dirType,
|
||||
isComponent: true,
|
||||
template:
|
||||
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}`);
|
||||
}
|
||||
});
|
||||
}
|
|
@ -20,11 +20,11 @@ import {TemplateParser} from '@angular/compiler/src/template_parser';
|
|||
import {createOfflineCompileUrlResolver} from '@angular/compiler/src/url_resolver';
|
||||
import {MODULE_SUFFIX} from '@angular/compiler/src/util';
|
||||
import {ViewCompiler} from '@angular/compiler/src/view_compiler/view_compiler';
|
||||
import {XHR} from '@angular/compiler/src/xhr';
|
||||
|
||||
import {Console} from '../core_private';
|
||||
import {IS_DART, isPresent, print} from '../src/facade/lang';
|
||||
import {MockSchemaRegistry} from '../testing/schema_registry_mock';
|
||||
import {MockXHR} from '../testing/xhr_mock';
|
||||
|
||||
|
||||
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();
|
||||
xhr.when(`${THIS_MODULE_PATH}/offline_compiler_compa.html`, 'Hello World {{user}}!');
|
||||
var htmlParser = new HtmlParser();
|
||||
var config = new CompilerConfig({genDebugInfo: true, useJit: true});
|
||||
var normalizer = new DirectiveNormalizer(xhr, urlResolver, htmlParser, config);
|
||||
|
@ -54,24 +53,25 @@ function _createOfflineCompiler(xhr: MockXHR, emitter: OutputEmitter): OfflineCo
|
|||
normalizer,
|
||||
new TemplateParser(
|
||||
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(
|
||||
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 result = compiler.normalizeDirectiveMetadata(comp).then((normComp) => {
|
||||
return compiler.compileTemplates([new NormalizedComponentWithViewDirectives(normComp, [], [])])
|
||||
return compiler
|
||||
.compileTemplates([new NormalizedComponentWithViewDirectives(normComp, [], [])])[0]
|
||||
.source;
|
||||
});
|
||||
xhr.flush();
|
||||
return result;
|
||||
}
|
||||
|
||||
export class SimpleJsImportGenerator implements ImportGenerator {
|
||||
getImportPath(moduleUrlStr: string, importedUrlStr: string): string {
|
||||
// var moduleAssetUrl = ImportGenerator.parseAssetUrl(moduleUrlStr);
|
||||
var importedAssetUrl = ImportGenerator.parseAssetUrl(importedUrlStr);
|
||||
if (isPresent(importedAssetUrl)) {
|
||||
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}`);
|
||||
}
|
||||
}
|
|
@ -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');
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
|
@ -9,7 +9,7 @@
|
|||
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 {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 {TimerWrapper} from '../src/facade/async';
|
||||
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) {
|
||||
describe('ComponentFixture', () => {
|
||||
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();
|
||||
});
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -6,18 +6,19 @@
|
|||
* 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 {MockViewResolver} from '../testing';
|
||||
import {Component, ViewMetadata} from '@angular/core';
|
||||
import {Component, ViewMetadata, Injector} from '@angular/core';
|
||||
import {isBlank} from '../src/facade/lang';
|
||||
|
||||
export function main() {
|
||||
describe('MockViewResolver', () => {
|
||||
var viewResolver: MockViewResolver;
|
||||
|
||||
beforeEach(() => { viewResolver = new MockViewResolver(); });
|
||||
beforeEach(inject(
|
||||
[Injector], (injector: Injector) => { viewResolver = new MockViewResolver(injector); }));
|
||||
|
||||
describe('View overriding', () => {
|
||||
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);
|
||||
});
|
||||
|
||||
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);
|
||||
expect(() => {
|
||||
viewResolver.setView(SomeComponent, new ViewMetadata({template: 'overridden template'}));
|
||||
})
|
||||
.toThrowError(
|
||||
`The component ${stringify(SomeComponent)} has already been compiled, its configuration can not be changed`);
|
||||
viewResolver.setView(SomeComponent, new ViewMetadata({template: 'overridden template'}));
|
||||
var view = viewResolver.resolve(SomeComponent);
|
||||
expect(view.template).toEqual('overridden template');
|
||||
expect(isBlank(view.directives)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -58,11 +58,11 @@ export function main() {
|
|||
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);
|
||||
expect(() => { viewResolver.setInlineTemplate(SomeComponent, 'overridden template'); })
|
||||
.toThrowError(
|
||||
`The component ${stringify(SomeComponent)} has already been compiled, its configuration can not be changed`);
|
||||
viewResolver.setInlineTemplate(SomeComponent, 'overridden template');
|
||||
var view = viewResolver.resolve(SomeComponent);
|
||||
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)}`);
|
||||
});
|
||||
|
||||
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);
|
||||
expect(() => {
|
||||
viewResolver.overrideViewDirective(SomeComponent, SomeDirective, SomeOtherDirective);
|
||||
})
|
||||
.toThrowError(
|
||||
`The component ${stringify(SomeComponent)} has already been compiled, its configuration can not be changed`);
|
||||
viewResolver.overrideViewDirective(SomeComponent, SomeDirective, SomeOtherDirective);
|
||||
var view = viewResolver.resolve(SomeComponent);
|
||||
expect(view.directives.length).toEqual(1);
|
||||
expect(view.directives[0]).toBe(SomeOtherDirective);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,13 +6,14 @@
|
|||
* 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 {Map} from '../src/facade/collection';
|
||||
import {Type, isPresent} from '../src/facade/lang';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* An implementation of {@link DirectiveResolver} that allows overriding
|
||||
* various properties of directives.
|
||||
|
@ -22,6 +23,10 @@ export class MockDirectiveResolver extends DirectiveResolver {
|
|||
private _providerOverrides = 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 {
|
||||
var dm = super.resolve(type);
|
||||
|
||||
|
@ -69,9 +74,11 @@ export class MockDirectiveResolver extends DirectiveResolver {
|
|||
|
||||
setProvidersOverride(type: Type, providers: any[]): void {
|
||||
this._providerOverrides.set(type, providers);
|
||||
this._compiler.clearCacheFor(type);
|
||||
}
|
||||
|
||||
setViewProvidersOverride(type: Type, viewProviders: any[]): void {
|
||||
this.viewProviderOverrides.set(type, viewProviders);
|
||||
this._compiler.clearCacheFor(type);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,39 +6,36 @@
|
|||
* 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 {DirectiveResolver, ViewResolver} from '../index';
|
||||
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
|
||||
*/
|
||||
export {TestComponentRenderer} from '@angular/core/testing';
|
||||
|
||||
/**
|
||||
* @deprecated Import TestComponentBuilder from @angular/core/testing
|
||||
*/
|
||||
export {TestComponentBuilder} from '@angular/core/testing';
|
||||
|
||||
/**
|
||||
* @deprecated Import ComponentFixture from @angular/core/testing
|
||||
*/
|
||||
export {ComponentFixture} from '@angular/core/testing';
|
||||
|
||||
/**
|
||||
* @deprecated Import ComponentFixtureNoNgZone from @angular/core/testing
|
||||
*/
|
||||
export {ComponentFixtureNoNgZone} from '@angular/core/testing';
|
||||
|
||||
/**
|
||||
* @deprecated Import ComponentFixtureAutoDetect from @angular/core/testing
|
||||
*/
|
||||
export {ComponentFixtureAutoDetect} from '@angular/core/testing';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A TestComponentBuilder that allows overriding based on the compiler.
|
||||
*/
|
||||
|
@ -114,32 +111,31 @@ export class OverridingTestComponentBuilder extends TestComponentBuilder {
|
|||
return clone;
|
||||
}
|
||||
|
||||
createAsync(rootComponentType: Type): Promise<ComponentFixture<any>> {
|
||||
let noNgZone = IS_DART || this._injector.get(ComponentFixtureNoNgZone, false);
|
||||
let ngZone: NgZone = noNgZone ? null : this._injector.get(NgZone, null);
|
||||
createAsync<T>(rootComponentType: ConcreteType<T>): Promise<ComponentFixture<T>> {
|
||||
this._applyMetadataOverrides();
|
||||
return super.createAsync(rootComponentType);
|
||||
}
|
||||
|
||||
let initComponent = () => {
|
||||
let mockDirectiveResolver = this._injector.get(DirectiveResolver);
|
||||
let mockViewResolver = this._injector.get(ViewResolver);
|
||||
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));
|
||||
createSync<T>(rootComponentType: ConcreteType<T>): ComponentFixture<T> {
|
||||
this._applyMetadataOverrides();
|
||||
return super.createSync(rootComponentType);
|
||||
}
|
||||
|
||||
let promise: Promise<ComponentFactory<any>> =
|
||||
this._injector.get(ComponentResolver).resolveComponent(rootComponentType);
|
||||
return promise.then(componentFactory => this.createFromFactory(ngZone, componentFactory));
|
||||
};
|
||||
|
||||
return ngZone == null ? initComponent() : ngZone.run(initComponent);
|
||||
private _applyMetadataOverrides() {
|
||||
let mockDirectiveResolver = this._injector.get(DirectiveResolver);
|
||||
let mockViewResolver = this._injector.get(ViewResolver);
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* 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 {Map} from '../src/facade/collection';
|
||||
|
@ -21,38 +21,38 @@ export class MockViewResolver extends ViewResolver {
|
|||
/** @internal */
|
||||
_animations = new Map<Type, AnimationEntryMetadata[]>();
|
||||
/** @internal */
|
||||
_viewCache = new Map<Type, ViewMetadata>();
|
||||
/** @internal */
|
||||
_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.
|
||||
*/
|
||||
setView(component: Type, view: ViewMetadata): void {
|
||||
this._checkOverrideable(component);
|
||||
this._views.set(component, view);
|
||||
this._clearCacheFor(component);
|
||||
}
|
||||
/**
|
||||
* Overrides the inline template for a component - other configuration remains unchanged.
|
||||
*/
|
||||
setInlineTemplate(component: Type, template: string): void {
|
||||
this._checkOverrideable(component);
|
||||
this._inlineTemplates.set(component, template);
|
||||
this._clearCacheFor(component);
|
||||
}
|
||||
|
||||
setAnimations(component: Type, animations: AnimationEntryMetadata[]): void {
|
||||
this._checkOverrideable(component);
|
||||
this._animations.set(component, animations);
|
||||
this._clearCacheFor(component);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides a directive from the component {@link ViewMetadata}.
|
||||
*/
|
||||
overrideViewDirective(component: Type, from: Type, to: Type): void {
|
||||
this._checkOverrideable(component);
|
||||
|
||||
var overrides = this._directiveOverrides.get(component);
|
||||
|
||||
if (isBlank(overrides)) {
|
||||
|
@ -61,6 +61,7 @@ export class MockViewResolver extends ViewResolver {
|
|||
}
|
||||
|
||||
overrides.set(from, to);
|
||||
this._clearCacheFor(component);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -72,10 +73,7 @@ export class MockViewResolver extends ViewResolver {
|
|||
* - Override the @View definition, see `setInlineTemplate`.
|
||||
*/
|
||||
resolve(component: Type): ViewMetadata {
|
||||
var view = this._viewCache.get(component);
|
||||
if (isPresent(view)) return view;
|
||||
|
||||
view = this._views.get(component);
|
||||
var view = this._views.get(component);
|
||||
if (isBlank(view)) {
|
||||
view = super.resolve(component);
|
||||
}
|
||||
|
@ -123,26 +121,8 @@ export class MockViewResolver extends ViewResolver {
|
|||
interpolation: view.interpolation
|
||||
});
|
||||
|
||||
this._viewCache.set(component, 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 {
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
|
||||
// Public API for compiler
|
||||
export {Compiler} from './linker/compiler';
|
||||
export {ComponentFactory, ComponentRef} from './linker/component_factory';
|
||||
export {ComponentFactoryResolver, NoComponentFactoryError} from './linker/component_factory_resolver';
|
||||
export {ComponentResolver} from './linker/component_resolver';
|
||||
|
|
|
@ -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) {}
|
||||
}
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import {Inject, OpaqueToken, Optional, SkipSelf} from '../di';
|
||||
import {BaseException} from '../facade/exceptions';
|
||||
import {ClassWithConstructor, stringify} from '../facade/lang';
|
||||
import {ConcreteType, stringify} from '../facade/lang';
|
||||
|
||||
import {ComponentFactory} from './component_factory';
|
||||
|
||||
|
@ -33,7 +33,7 @@ class _NullComponentFactoryResolver implements ComponentFactoryResolver {
|
|||
*/
|
||||
export abstract class ComponentFactoryResolver {
|
||||
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 {
|
||||
|
|
|
@ -19,7 +19,7 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
|
|||
|
||||
isReflectionEnabled(): boolean { return true; }
|
||||
|
||||
factory(t: ConcreteType): Function {
|
||||
factory(t: ConcreteType<any>): Function {
|
||||
switch (t.length) {
|
||||
case 0:
|
||||
return () => new t();
|
||||
|
|
|
@ -84,7 +84,7 @@ export interface TypeDecorator {
|
|||
/**
|
||||
* 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 {
|
||||
|
@ -219,7 +219,7 @@ function applyParams(fnOrArray: (Function | any[]), key: string): Function {
|
|||
* ```
|
||||
* @stable
|
||||
*/
|
||||
export function Class(clsDef: ClassDefinition): ConcreteType {
|
||||
export function Class(clsDef: ClassDefinition): ConcreteType<any> {
|
||||
var constructor = applyParams(
|
||||
clsDef.hasOwnProperty('constructor') ? clsDef.constructor : undefined, 'constructor');
|
||||
var proto = constructor.prototype;
|
||||
|
@ -246,7 +246,7 @@ export function Class(clsDef: ClassDefinition): ConcreteType {
|
|||
(constructor as any /** TODO #9100 */)['overriddenName'] = `class${_nextClassId++}`;
|
||||
}
|
||||
|
||||
return <ConcreteType>constructor;
|
||||
return <ConcreteType<any>>constructor;
|
||||
}
|
||||
|
||||
var Reflect = global.Reflect;
|
||||
|
|
|
@ -10,7 +10,7 @@ import {TestComponentBuilder} from '@angular/compiler/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 {isBlank, NumberWrapper,} from '../../src/facade/lang';
|
||||
import {isBlank, NumberWrapper, ConcreteType,} from '../../src/facade/lang';
|
||||
import {BaseException} from '../../src/facade/exceptions';
|
||||
import {StringMapWrapper} from '../../src/facade/collection';
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
|
@ -40,9 +40,9 @@ export function main() {
|
|||
var renderLog: RenderLog;
|
||||
var directiveLog: DirectiveLog;
|
||||
|
||||
function createCompFixture(
|
||||
template: string, compType: Type = TestComponent,
|
||||
_tcb: TestComponentBuilder = null): ComponentFixture<any> {
|
||||
function createCompFixture<T>(
|
||||
template: string, compType: ConcreteType<T> = <any>TestComponent,
|
||||
_tcb: TestComponentBuilder = null): ComponentFixture<T> {
|
||||
if (isBlank(_tcb)) {
|
||||
_tcb = tcb;
|
||||
}
|
||||
|
@ -58,18 +58,19 @@ export function main() {
|
|||
return nodes.map(node => node.injector.get(dirType));
|
||||
}
|
||||
|
||||
function _bindSimpleProp(
|
||||
bindAttr: string, compType: Type = TestComponent): ComponentFixture<any> {
|
||||
function _bindSimpleProp<T>(
|
||||
bindAttr: string, compType: ConcreteType<T> = <any>TestComponent): ComponentFixture<T> {
|
||||
var template = `<div ${bindAttr}></div>`;
|
||||
return createCompFixture(template, compType);
|
||||
}
|
||||
|
||||
function _bindSimpleValue(
|
||||
expression: any, compType: Type = TestComponent): ComponentFixture<any> {
|
||||
function _bindSimpleValue<T>(
|
||||
expression: any, compType: ConcreteType<T> = <any>TestComponent): ComponentFixture<T> {
|
||||
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);
|
||||
ctx.detectChanges(false);
|
||||
return renderLog.log;
|
||||
|
|
|
@ -1504,20 +1504,15 @@ function declareTests({useJit}: {useJit: boolean}) {
|
|||
|
||||
describe('error handling', () => {
|
||||
it('should report a meaningful error when a directive is missing annotation',
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
tcb = tcb.overrideView(
|
||||
MyComp,
|
||||
new ViewMetadata({template: '', directives: [SomeDirectiveMissingAnnotation]}));
|
||||
inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
|
||||
tcb = tcb.overrideView(
|
||||
MyComp,
|
||||
new ViewMetadata({template: '', directives: [SomeDirectiveMissingAnnotation]}));
|
||||
|
||||
PromiseWrapper.catchError(tcb.createAsync(MyComp), (e) => {
|
||||
expect(e.message).toEqual(
|
||||
`No Directive annotation found on ${stringify(SomeDirectiveMissingAnnotation)}`);
|
||||
async.done();
|
||||
return null;
|
||||
});
|
||||
}));
|
||||
expect(() => tcb.createAsync(MyComp))
|
||||
.toThrowError(
|
||||
`No Directive annotation found on ${stringify(SomeDirectiveMissingAnnotation)}`);
|
||||
}));
|
||||
|
||||
it('should report a meaningful error when a component is missing view annotation',
|
||||
inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
|
||||
|
@ -1530,19 +1525,13 @@ function declareTests({useJit}: {useJit: boolean}) {
|
|||
}));
|
||||
|
||||
it('should report a meaningful error when a directive is null',
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
tcb =
|
||||
tcb.overrideView(MyComp, new ViewMetadata({directives: [[null]], template: ''}));
|
||||
inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
|
||||
tcb = tcb.overrideView(MyComp, new ViewMetadata({directives: [[null]], template: ''}));
|
||||
|
||||
PromiseWrapper.catchError(tcb.createAsync(MyComp), (e) => {
|
||||
expect(e.message).toEqual(
|
||||
`Unexpected directive value 'null' on the View of component '${stringify(MyComp)}'`);
|
||||
async.done();
|
||||
return null;
|
||||
});
|
||||
}));
|
||||
expect(() => tcb.createAsync(MyComp))
|
||||
.toThrowError(
|
||||
`Unexpected directive value 'null' on the View of component '${stringify(MyComp)}'`);
|
||||
}));
|
||||
|
||||
it('should provide an error context when an error happens in DI',
|
||||
inject(
|
||||
|
@ -1642,22 +1631,17 @@ function declareTests({useJit}: {useJit: boolean}) {
|
|||
|
||||
if (!IS_DART) {
|
||||
it('should report a meaningful error when a directive is undefined',
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
|
||||
|
||||
var undefinedValue: any = void(0);
|
||||
var undefinedValue: any = void(0);
|
||||
|
||||
tcb = tcb.overrideView(
|
||||
MyComp, new ViewMetadata({directives: [undefinedValue], template: ''}));
|
||||
tcb = tcb.overrideView(
|
||||
MyComp, new ViewMetadata({directives: [undefinedValue], template: ''}));
|
||||
|
||||
PromiseWrapper.catchError(tcb.createAsync(MyComp), (e) => {
|
||||
expect(e.message).toEqual(
|
||||
`Unexpected directive value 'undefined' on the View of component '${stringify(MyComp)}'`);
|
||||
async.done();
|
||||
return null;
|
||||
});
|
||||
}));
|
||||
expect(() => tcb.createAsync(MyComp))
|
||||
.toThrowError(
|
||||
`Unexpected directive value 'undefined' on the View of component '${stringify(MyComp)}'`);
|
||||
}));
|
||||
}
|
||||
|
||||
it('should specify a location of an error that happened during change detection (text)',
|
||||
|
|
|
@ -261,18 +261,14 @@ export function main() {
|
|||
}));
|
||||
|
||||
it('should throw with descriptive error when query selectors are not present',
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
tcb.overrideTemplate(
|
||||
MyCompBroken0, '<has-null-query-condition></has-null-query-condition>')
|
||||
.createAsync(MyCompBroken0)
|
||||
.catch((e) => {
|
||||
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();
|
||||
});
|
||||
}));
|
||||
inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
|
||||
expect(
|
||||
() => tcb.overrideTemplate(
|
||||
MyCompBroken0, '<has-null-query-condition></has-null-query-condition>')
|
||||
.createAsync(MyCompBroken0))
|
||||
.toThrowError(
|
||||
`Can't construct a query for the property "errorTrigger" of "${stringify(HasNullQueryCondition)}" since the query selector wasn't defined.`);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('query for TemplateRef', () => {
|
||||
|
|
|
@ -12,7 +12,7 @@ import {AsyncTestCompleter} from '@angular/core/testing/testing_internal';
|
|||
|
||||
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 {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',
|
||||
inject(
|
||||
[TestComponentBuilder, AsyncTestCompleter],
|
||||
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
|
||||
[TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async: any) => {
|
||||
tcb.overrideView(
|
||||
MyComp1, new ViewMetadata({
|
||||
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>`})
|
||||
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 {
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
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 {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 {NgIf, NgFor} from '@angular/common';
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
|
@ -242,10 +242,11 @@ class TestComp {
|
|||
export function main() {
|
||||
var tcb: TestComponentBuilder;
|
||||
|
||||
function createCompFixture(
|
||||
template: string, tcb: TestComponentBuilder, comp: Type = null): ComponentFixture<any> {
|
||||
function createCompFixture<T>(
|
||||
template: string, tcb: TestComponentBuilder,
|
||||
comp: ConcreteType<T> = null): ComponentFixture<T> {
|
||||
if (isBlank(comp)) {
|
||||
comp = TestComp;
|
||||
comp = <any>TestComp;
|
||||
}
|
||||
return tcb
|
||||
.overrideView(
|
||||
|
@ -255,7 +256,7 @@ export function main() {
|
|||
}
|
||||
|
||||
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);
|
||||
fixture.detectChanges();
|
||||
return fixture.debugElement;
|
||||
|
|
|
@ -6,14 +6,15 @@
|
|||
* 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 {IS_DART, Type, isPresent} from '../src/facade/lang';
|
||||
import {ConcreteType, IS_DART, Type, isPresent} from '../src/facade/lang';
|
||||
|
||||
import {ComponentFixture} from './component_fixture';
|
||||
import {tick} from './fake_async';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
createAsync(rootComponentType: Type): Promise<ComponentFixture<any>> {
|
||||
createAsync<T>(rootComponentType: ConcreteType<T>): Promise<ComponentFixture<T>> {
|
||||
let noNgZone = IS_DART || this._injector.get(ComponentFixtureNoNgZone, false);
|
||||
let ngZone: NgZone = noNgZone ? null : this._injector.get(NgZone, null);
|
||||
let compiler: Compiler = this._injector.get(Compiler);
|
||||
|
||||
let initComponent = () => {
|
||||
let promise: Promise<ComponentFactory<any>> =
|
||||
this._injector.get(ComponentResolver).resolveComponent(rootComponentType);
|
||||
compiler.compileComponentAsync(rootComponentType);
|
||||
return promise.then(componentFactory => this.createFromFactory(ngZone, componentFactory));
|
||||
};
|
||||
|
||||
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 error: any /** TODO #9100 */;
|
||||
PromiseWrapper.then(
|
||||
|
@ -144,15 +146,16 @@ export class TestComponentBuilder {
|
|||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated createSync will be replaced with the ability to precompile components from within
|
||||
* the test.
|
||||
*/
|
||||
createSync<C>(componentFactory: ComponentFactory<C>): ComponentFixture<C> {
|
||||
createSync<T>(rootComponentType: ConcreteType<T>): ComponentFixture<T> {
|
||||
let noNgZone = IS_DART || this._injector.get(ComponentFixtureNoNgZone, false);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,9 +77,7 @@ export interface Type extends Function {}
|
|||
/**
|
||||
* Runtime representation of a type that is constructable (non-abstract).
|
||||
*/
|
||||
export interface ConcreteType extends Type { new (...args: any[] /** TODO #9100 */): any; }
|
||||
|
||||
export interface ClassWithConstructor<T> { new (...args: any[]): T; }
|
||||
export interface ConcreteType<T> extends Type { new (...args: any[]): T; }
|
||||
|
||||
export function getTypeNameForDebugging(type: Type): string {
|
||||
if (type['name']) {
|
||||
|
|
|
@ -310,21 +310,24 @@ export declare class CompilerConfig {
|
|||
});
|
||||
}
|
||||
|
||||
export declare class CompileTemplateMetadata {
|
||||
export declare class CompileTemplateMetadata implements CompileStylesheetMetadata {
|
||||
animations: CompileAnimationEntryMetadata[];
|
||||
encapsulation: ViewEncapsulation;
|
||||
externalStylesheets: CompileStylesheetMetadata[];
|
||||
interpolation: [string, string];
|
||||
moduleUrl: string;
|
||||
ngContentSelectors: string[];
|
||||
styleUrls: string[];
|
||||
styles: string[];
|
||||
template: string;
|
||||
templateUrl: string;
|
||||
constructor({encapsulation, template, templateUrl, styles, styleUrls, animations, ngContentSelectors, interpolation}?: {
|
||||
constructor({encapsulation, template, templateUrl, styles, styleUrls, externalStylesheets, animations, ngContentSelectors, interpolation}?: {
|
||||
encapsulation?: ViewEncapsulation;
|
||||
template?: string;
|
||||
templateUrl?: string;
|
||||
styles?: string[];
|
||||
styleUrls?: string[];
|
||||
externalStylesheets?: CompileStylesheetMetadata[];
|
||||
ngContentSelectors?: string[];
|
||||
animations?: CompileAnimationEntryMetadata[];
|
||||
interpolation?: [string, string];
|
||||
|
@ -460,9 +463,8 @@ export declare class NormalizedComponentWithViewDirectives {
|
|||
}
|
||||
|
||||
export declare class OfflineCompiler {
|
||||
constructor(_directiveNormalizer: DirectiveNormalizer, _templateParser: TemplateParser, _styleCompiler: StyleCompiler, _viewCompiler: ViewCompiler, _outputEmitter: OutputEmitter, _xhr: XHR);
|
||||
compileTemplates(components: NormalizedComponentWithViewDirectives[]): SourceModule;
|
||||
loadAndCompileStylesheet(stylesheetUrl: string, shim: boolean, suffix: string): Promise<StyleSheetSourceWithImports>;
|
||||
constructor(_directiveNormalizer: DirectiveNormalizer, _templateParser: TemplateParser, _styleCompiler: StyleCompiler, _viewCompiler: ViewCompiler, _outputEmitter: OutputEmitter);
|
||||
compileTemplates(components: NormalizedComponentWithViewDirectives[]): SourceModule[];
|
||||
normalizeDirectiveMetadata(directive: CompileDirectiveMetadata): Promise<CompileDirectiveMetadata>;
|
||||
}
|
||||
|
||||
|
@ -515,9 +517,12 @@ export declare abstract class RenderTypes {
|
|||
renderer: CompileIdentifierMetadata;
|
||||
}
|
||||
|
||||
export declare class RuntimeCompiler implements ComponentResolver {
|
||||
constructor(_metadataResolver: CompileMetadataResolver, _templateNormalizer: DirectiveNormalizer, _templateParser: TemplateParser, _styleCompiler: StyleCompiler, _viewCompiler: ViewCompiler, _xhr: XHR, _genConfig: CompilerConfig);
|
||||
export declare class RuntimeCompiler implements ComponentResolver, Compiler {
|
||||
constructor(_metadataResolver: CompileMetadataResolver, _templateNormalizer: DirectiveNormalizer, _templateParser: TemplateParser, _styleCompiler: StyleCompiler, _viewCompiler: ViewCompiler, _genConfig: CompilerConfig);
|
||||
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>>;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
export declare class MockDirectiveResolver extends DirectiveResolver {
|
||||
constructor(_injector: Injector);
|
||||
resolve(type: Type): DirectiveMetadata;
|
||||
setProvidersOverride(type: Type, providers: any[]): void;
|
||||
setViewProvidersOverride(type: Type, viewProviders: any[]): void;
|
||||
|
@ -22,7 +23,7 @@ export declare class MockSchemaRegistry implements ElementSchemaRegistry {
|
|||
}
|
||||
|
||||
export declare class MockViewResolver extends ViewResolver {
|
||||
constructor();
|
||||
constructor(_injector: Injector);
|
||||
overrideViewDirective(component: Type, from: Type, to: Type): void;
|
||||
resolve(component: Type): ViewMetadata;
|
||||
setAnimations(component: Type, animations: AnimationEntryMetadata[]): void;
|
||||
|
@ -32,7 +33,8 @@ export declare class MockViewResolver extends ViewResolver {
|
|||
|
||||
export declare class OverridingTestComponentBuilder extends TestComponentBuilder {
|
||||
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;
|
||||
overrideDirective(componentType: Type, from: Type, to: Type): OverridingTestComponentBuilder;
|
||||
overrideProviders(type: Type, providers: any[]): OverridingTestComponentBuilder;
|
||||
|
|
|
@ -207,7 +207,7 @@ export declare abstract class ChangeDetectorRef {
|
|||
}
|
||||
|
||||
/** @stable */
|
||||
export declare function Class(clsDef: ClassDefinition): ConcreteType;
|
||||
export declare function Class(clsDef: ClassDefinition): ConcreteType<any>;
|
||||
|
||||
/** @stable */
|
||||
export interface ClassDefinition {
|
||||
|
@ -226,6 +226,14 @@ export declare class CollectionChangeRecord {
|
|||
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 */
|
||||
export declare var Component: ComponentMetadataFactory;
|
||||
|
||||
|
@ -254,7 +262,7 @@ export declare class ComponentFactory<C> {
|
|||
|
||||
/** @stable */
|
||||
export declare abstract class ComponentFactoryResolver {
|
||||
abstract resolveComponentFactory<T>(component: ClassWithConstructor<T>): ComponentFactory<T>;
|
||||
abstract resolveComponentFactory<T>(component: ConcreteType<T>): ComponentFactory<T>;
|
||||
static NULL: ComponentFactoryResolver;
|
||||
}
|
||||
|
||||
|
@ -1267,7 +1275,7 @@ export interface TypeDecorator {
|
|||
annotations: any[];
|
||||
(target: Object, propertyKey?: string | symbol, parameterIndex?: number): void;
|
||||
<T extends Type>(type: T): T;
|
||||
Class(obj: ClassDefinition): ConcreteType;
|
||||
Class(obj: ClassDefinition): ConcreteType<any>;
|
||||
}
|
||||
|
||||
/** @stable */
|
||||
|
|
|
@ -89,10 +89,10 @@ export declare function setBaseTestProviders(platformProviders: Array<Type | Pro
|
|||
export declare class TestComponentBuilder {
|
||||
protected _injector: Injector;
|
||||
constructor(_injector: Injector);
|
||||
createAsync(rootComponentType: Type): Promise<ComponentFixture<any>>;
|
||||
createFakeAsync(rootComponentType: Type): ComponentFixture<any>;
|
||||
createAsync<T>(rootComponentType: ConcreteType<T>): Promise<ComponentFixture<T>>;
|
||||
createFakeAsync<T>(rootComponentType: ConcreteType<T>): ComponentFixture<T>;
|
||||
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;
|
||||
overrideDirective(componentType: Type, from: Type, to: Type): TestComponentBuilder;
|
||||
overrideProviders(type: Type, providers: any[]): TestComponentBuilder;
|
||||
|
|
Loading…
Reference in New Issue