From baa654a2342655f5b54399b2b6514e8a64f37cd2 Mon Sep 17 00:00:00 2001 From: Tobias Bosch Date: Thu, 2 Feb 2017 15:01:35 -0800 Subject: [PATCH] feat(compiler): integrate compiler with view engine - main integration tests work (#14284) Part of #14013 PR Close #14284 --- .../src/compiler_util/expression_converter.ts | 2 +- modules/@angular/compiler/src/config.ts | 11 +- .../src/directive_wrapper_compiler.ts | 4 +- modules/@angular/compiler/src/identifiers.ts | 44 +- modules/@angular/compiler/src/jit/compiler.ts | 10 +- .../compiler/src/jit/compiler_factory.ts | 15 +- .../compiler/src/metadata_resolver.ts | 12 +- .../compiler/src/private_import_core.ts | 1 + .../compiler/src/provider_analyzer.ts | 91 ++- .../src/template_parser/template_ast.ts | 24 +- .../src/template_parser/template_parser.ts | 13 +- .../src/view_compiler/event_binder.ts | 4 +- .../compiler/src/view_compiler_next/README.md | 5 + .../src/view_compiler_next/view_compiler.ts | 738 ++++++++++++++++++ .../template_parser/template_parser_spec.ts | 12 +- .../@angular/core/src/application_module.ts | 8 +- .../@angular/core/src/core_private_export.ts | 3 + modules/@angular/core/src/view/element.ts | 16 +- modules/@angular/core/src/view/index.ts | 2 +- modules/@angular/core/src/view/provider.ts | 9 +- modules/@angular/core/src/view/refs.ts | 6 +- modules/@angular/core/src/view/renderer.ts | 19 +- modules/@angular/core/src/view/services.ts | 104 ++- modules/@angular/core/src/view/types.ts | 16 +- modules/@angular/core/src/view/util.ts | 42 +- modules/@angular/core/src/view/view.ts | 35 +- .../core/test/linker/integration_spec.ts | 103 +-- .../core/test/view/component_view_spec.ts | 8 +- .../@angular/core/test/view/element_spec.ts | 4 +- .../core/test/view/embedded_view_spec.ts | 8 +- .../core/test/view/ng_content_spec.ts | 6 +- .../@angular/core/test/view/provider_spec.ts | 6 +- modules/@angular/core/test/view/query_spec.ts | 72 +- .../@angular/core/test/view/view_def_spec.ts | 4 +- modules/benchmarks/src/tree/ng2_next/tree.ts | 52 +- 35 files changed, 1232 insertions(+), 277 deletions(-) create mode 100644 modules/@angular/compiler/src/view_compiler_next/README.md create mode 100644 modules/@angular/compiler/src/view_compiler_next/view_compiler.ts diff --git a/modules/@angular/compiler/src/compiler_util/expression_converter.ts b/modules/@angular/compiler/src/compiler_util/expression_converter.ts index b4b937e684..099b1afe5f 100644 --- a/modules/@angular/compiler/src/compiler_util/expression_converter.ts +++ b/modules/@angular/compiler/src/compiler_util/expression_converter.ts @@ -71,7 +71,7 @@ export function convertPropertyBinding( } export class ConvertActionBindingResult { - constructor(public stmts: o.Statement[], public preventDefault: o.ReadVarExpr) {} + constructor(public stmts: o.Statement[], public allowDefault: o.ReadVarExpr) {} } /** diff --git a/modules/@angular/compiler/src/config.ts b/modules/@angular/compiler/src/config.ts index c88ebdfb90..1619006f2d 100644 --- a/modules/@angular/compiler/src/config.ts +++ b/modules/@angular/compiler/src/config.ts @@ -6,11 +6,20 @@ * found in the LICENSE file at https://angular.io/license */ -import {MissingTranslationStrategy, ViewEncapsulation, isDevMode} from '@angular/core'; +import {InjectionToken, MissingTranslationStrategy, ViewEncapsulation, isDevMode} from '@angular/core'; import {CompileIdentifierMetadata} from './compile_metadata'; import {Identifiers, createIdentifier} from './identifiers'; + +/** + * Temporal switch for the compiler to use the new view engine, + * until it is fully integrated. + * + * Only works in Jit for now. + */ +export const USE_VIEW_ENGINE = new InjectionToken('UseViewEngine'); + export class CompilerConfig { public renderTypes: RenderTypes; public defaultEncapsulation: ViewEncapsulation; diff --git a/modules/@angular/compiler/src/directive_wrapper_compiler.ts b/modules/@angular/compiler/src/directive_wrapper_compiler.ts index 2c4f1b6f76..5ee6a9ec4c 100644 --- a/modules/@angular/compiler/src/directive_wrapper_compiler.ts +++ b/modules/@angular/compiler/src/directive_wrapper_compiler.ts @@ -288,8 +288,8 @@ function addHandleEventMethod(hostListeners: BoundEventAst[], builder: Directive builder, null, o.THIS_EXPR.prop(CONTEXT_FIELD_NAME), hostListener.handler, `sub_${eventIdx}`); const trueStmts = evalResult.stmts; - if (evalResult.preventDefault) { - trueStmts.push(resultVar.set(evalResult.preventDefault.and(resultVar)).toStmt()); + if (evalResult.allowDefault) { + trueStmts.push(resultVar.set(evalResult.allowDefault.and(resultVar)).toStmt()); } // TODO(tbosch): convert this into a `switch` once our OutputAst supports it. actionStmts.push( diff --git a/modules/@angular/compiler/src/identifiers.ts b/modules/@angular/compiler/src/identifiers.ts index c558841477..a8d314682d 100644 --- a/modules/@angular/compiler/src/identifiers.ts +++ b/modules/@angular/compiler/src/identifiers.ts @@ -9,10 +9,11 @@ import {ANALYZE_FOR_ENTRY_COMPONENTS, ChangeDetectionStrategy, ChangeDetectorRef, ComponentFactory, ComponentFactoryResolver, ComponentRef, ElementRef, Injector, LOCALE_ID, NgModuleFactory, QueryList, RenderComponentType, Renderer, SecurityContext, SimpleChange, TRANSLATIONS_FORMAT, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core'; import {CompileIdentifierMetadata, CompileTokenMetadata} from './compile_metadata'; -import {AnimationGroupPlayer, AnimationKeyframe, AnimationSequencePlayer, AnimationStyles, AnimationTransition, AppView, ChangeDetectorStatus, CodegenComponentFactoryResolver, ComponentRef_, DebugAppView, DebugContext, NgModuleInjector, NoOpAnimationPlayer, StaticNodeDebugInfo, TemplateRef_, ValueUnwrapper, ViewContainer, ViewType, balanceAnimationKeyframes, clearStyles, collectAndResolveStyles, devModeEqual, prepareFinalAnimationStyles, reflector, registerModuleFactory, renderStyles, view_utils} from './private_import_core'; +import {AnimationGroupPlayer, AnimationKeyframe, AnimationSequencePlayer, AnimationStyles, AnimationTransition, AppView, ChangeDetectorStatus, CodegenComponentFactoryResolver, ComponentRef_, DebugAppView, DebugContext, NgModuleInjector, NoOpAnimationPlayer, StaticNodeDebugInfo, TemplateRef_, ValueUnwrapper, ViewContainer, ViewType, balanceAnimationKeyframes, clearStyles, collectAndResolveStyles, devModeEqual, prepareFinalAnimationStyles, reflector, registerModuleFactory, renderStyles, viewEngine, view_utils} from './private_import_core'; const APP_VIEW_MODULE_URL = assetUrl('core', 'linker/view'); const VIEW_UTILS_MODULE_URL = assetUrl('core', 'linker/view_utils'); +const VIEW_ENGINE_MODULE_URL = assetUrl('core', 'view/index'); const CD_MODULE_URL = assetUrl('core', 'change_detection/change_detection'); const ANIMATION_STYLE_UTIL_ASSET_URL = assetUrl('core', 'animation/animation_style_util'); @@ -363,6 +364,47 @@ export class Identifiers { }; static noop: IdentifierSpec = {name: 'noop', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: view_utils.noop}; + + static viewDef: IdentifierSpec = { + name: 'viewDef', + moduleUrl: VIEW_ENGINE_MODULE_URL, + runtime: viewEngine.viewDef + }; + static elementDef: IdentifierSpec = { + name: 'elementDef', + moduleUrl: VIEW_ENGINE_MODULE_URL, + runtime: viewEngine.elementDef + }; + static anchorDef: IdentifierSpec = { + name: 'anchorDef', + moduleUrl: VIEW_ENGINE_MODULE_URL, + runtime: viewEngine.anchorDef + }; + static textDef: IdentifierSpec = { + name: 'textDef', + moduleUrl: VIEW_ENGINE_MODULE_URL, + runtime: viewEngine.textDef + }; + static directiveDef: IdentifierSpec = { + name: 'directiveDef', + moduleUrl: VIEW_ENGINE_MODULE_URL, + runtime: viewEngine.directiveDef + }; + static providerDef: IdentifierSpec = { + name: 'providerDef', + moduleUrl: VIEW_ENGINE_MODULE_URL, + runtime: viewEngine.providerDef + }; + static queryDef: IdentifierSpec = { + name: 'queryDef', + moduleUrl: VIEW_ENGINE_MODULE_URL, + runtime: viewEngine.queryDef + }; + static nodeValue: IdentifierSpec = { + name: 'nodeValue', + moduleUrl: VIEW_ENGINE_MODULE_URL, + runtime: viewEngine.nodeValue + }; } export function assetUrl(pkg: string, path: string = null, type: string = 'src'): string { diff --git a/modules/@angular/compiler/src/jit/compiler.ts b/modules/@angular/compiler/src/jit/compiler.ts index d9a4a6a837..6f497af5ac 100644 --- a/modules/@angular/compiler/src/jit/compiler.ts +++ b/modules/@angular/compiler/src/jit/compiler.ts @@ -6,12 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ -import {Compiler, ComponentFactory, Injector, ModuleWithComponentFactories, NgModuleFactory, Type} from '@angular/core'; +import {Compiler, ComponentFactory, Inject, Injector, ModuleWithComponentFactories, NgModuleFactory, Type} from '@angular/core'; import {AnimationCompiler} from '../animation/animation_compiler'; import {AnimationParser} from '../animation/animation_parser'; import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, ProviderMeta, ProxyClass, createHostComponentMeta, identifierName} from '../compile_metadata'; -import {CompilerConfig} from '../config'; +import {CompilerConfig, USE_VIEW_ENGINE} from '../config'; import {DirectiveWrapperCompiler} from '../directive_wrapper_compiler'; import {stringify} from '../facade/lang'; import {CompilerInjectable} from '../injectable'; @@ -50,7 +50,8 @@ export class JitCompiler implements Compiler { private _templateParser: TemplateParser, private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler, private _ngModuleCompiler: NgModuleCompiler, private _directiveWrapperCompiler: DirectiveWrapperCompiler, - private _compilerConfig: CompilerConfig, private _animationParser: AnimationParser) {} + private _compilerConfig: CompilerConfig, private _animationParser: AnimationParser, + @Inject(USE_VIEW_ENGINE) private _useViewEngine: boolean) {} get injector(): Injector { return this._injector; } @@ -244,6 +245,9 @@ export class JitCompiler implements Compiler { private _compileDirectiveWrapper( dirMeta: CompileDirectiveMetadata, moduleMeta: CompileNgModuleMetadata): void { + if (this._useViewEngine) { + return; + } const compileResult = this._directiveWrapperCompiler.compile(dirMeta); const statements = compileResult.statements; let directiveWrapperClass: any; diff --git a/modules/@angular/compiler/src/jit/compiler_factory.ts b/modules/@angular/compiler/src/jit/compiler_factory.ts index 0da4a2ff10..4d04a46c21 100644 --- a/modules/@angular/compiler/src/jit/compiler_factory.ts +++ b/modules/@angular/compiler/src/jit/compiler_factory.ts @@ -9,7 +9,7 @@ import {COMPILER_OPTIONS, Compiler, CompilerFactory, CompilerOptions, Inject, InjectionToken, MissingTranslationStrategy, Optional, PLATFORM_INITIALIZER, PlatformRef, Provider, ReflectiveInjector, TRANSLATIONS, TRANSLATIONS_FORMAT, Type, ViewEncapsulation, createPlatformFactory, isDevMode, platformCore} from '@angular/core'; import {AnimationParser} from '../animation/animation_parser'; -import {CompilerConfig} from '../config'; +import {CompilerConfig, USE_VIEW_ENGINE} from '../config'; import {DirectiveNormalizer} from '../directive_normalizer'; import {DirectiveResolver} from '../directive_resolver'; import {DirectiveWrapperCompiler} from '../directive_wrapper_compiler'; @@ -31,6 +31,7 @@ import {SummaryResolver} from '../summary_resolver'; import {TemplateParser} from '../template_parser/template_parser'; import {DEFAULT_PACKAGE_URL_PROVIDER, UrlResolver} from '../url_resolver'; import {ViewCompiler} from '../view_compiler/view_compiler'; +import {ViewCompilerNext} from '../view_compiler_next/view_compiler'; import {JitCompiler} from './compiler'; @@ -42,6 +43,11 @@ const _NO_RESOURCE_LOADER: ResourceLoader = { const baseHtmlParser = new InjectionToken('HtmlParser'); +function viewCompilerFactory( + useViewEngine: boolean, cc: CompilerConfig, sr: ElementSchemaRegistry) { + return useViewEngine ? new ViewCompilerNext(cc, sr) : new ViewCompiler(cc, sr); +} + /** * A set of providers that provide `JitCompiler` and its dependencies to use for * template compilation. @@ -81,7 +87,12 @@ export const COMPILER_PROVIDERS: Array|{[k: string]: any}|any[]> = CompileMetadataResolver, DEFAULT_PACKAGE_URL_PROVIDER, StyleCompiler, - ViewCompiler, + {provide: USE_VIEW_ENGINE, useValue: false}, + { + provide: ViewCompiler, + useFactory: viewCompilerFactory, + deps: [USE_VIEW_ENGINE, CompilerConfig, ElementSchemaRegistry] + }, NgModuleCompiler, DirectiveWrapperCompiler, {provide: CompilerConfig, useValue: new CompilerConfig()}, diff --git a/modules/@angular/compiler/src/metadata_resolver.ts b/modules/@angular/compiler/src/metadata_resolver.ts index afb5a2fd4d..3ff3301d38 100644 --- a/modules/@angular/compiler/src/metadata_resolver.ts +++ b/modules/@angular/compiler/src/metadata_resolver.ts @@ -12,6 +12,7 @@ import {StaticSymbol, StaticSymbolCache} from './aot/static_symbol'; import {ngfactoryFilePath} from './aot/util'; import {assertArrayOfStrings, assertInterpolationSymbols} from './assertions'; import * as cpl from './compile_metadata'; +import {USE_VIEW_ENGINE} from './config'; import {DirectiveNormalizer} from './directive_normalizer'; import {DirectiveResolver} from './directive_resolver'; import {stringify} from './facade/lang'; @@ -20,7 +21,7 @@ import {CompilerInjectable} from './injectable'; import {hasLifecycleHook} from './lifecycle_reflector'; import {NgModuleResolver} from './ng_module_resolver'; import {PipeResolver} from './pipe_resolver'; -import {ERROR_COMPONENT_TYPE, LIFECYCLE_HOOKS_VALUES, ReflectorReader, reflector} from './private_import_core'; +import {ERROR_COMPONENT_TYPE, LIFECYCLE_HOOKS_VALUES, ReflectorReader, reflector, viewEngine} from './private_import_core'; import {ElementSchemaRegistry} from './schema/element_schema_registry'; import {SummaryResolver} from './summary_resolver'; import {getUrlScheme} from './url_resolver'; @@ -53,7 +54,8 @@ export class CompileMetadataResolver { private _directiveNormalizer: DirectiveNormalizer, @Optional() private _staticSymbolCache: StaticSymbolCache, private _reflector: ReflectorReader = reflector, - @Optional() @Inject(ERROR_COLLECTOR_TOKEN) private _errorCollector?: ErrorCollector) {} + @Optional() @Inject(ERROR_COLLECTOR_TOKEN) private _errorCollector?: ErrorCollector, + @Optional() @Inject(USE_VIEW_ENGINE) private _useViewEngine?: boolean) {} clearCacheFor(type: Type) { const dirMeta = this._directiveCache.get(type); @@ -135,7 +137,11 @@ export class CompileMetadataResolver { ngfactoryFilePath(dirType.filePath), cpl.componentFactoryName(dirType)); } else { const hostView = this.getHostComponentViewClass(dirType); - return new ComponentFactory(selector, hostView, dirType); + if (this._useViewEngine) { + return viewEngine.createComponentFactory(selector, dirType, hostView); + } else { + return new ComponentFactory(selector, hostView, dirType); + } } } diff --git a/modules/@angular/compiler/src/private_import_core.ts b/modules/@angular/compiler/src/private_import_core.ts index 0bf71d57be..8f8811dbb6 100644 --- a/modules/@angular/compiler/src/private_import_core.ts +++ b/modules/@angular/compiler/src/private_import_core.ts @@ -29,6 +29,7 @@ export const registerModuleFactory: typeof r.registerModuleFactory = r.registerM export type ViewType = typeof r._ViewType; export const ViewType: typeof r.ViewType = r.ViewType; export const view_utils: typeof r.view_utils = r.view_utils; +export const viewEngine: typeof r.viewEngine = r.viewEngine; export const DebugContext: typeof r.DebugContext = r.DebugContext; export const StaticNodeDebugInfo: typeof r.StaticNodeDebugInfo = r.StaticNodeDebugInfo; export const devModeEqual: typeof r.devModeEqual = r.devModeEqual; diff --git a/modules/@angular/compiler/src/provider_analyzer.ts b/modules/@angular/compiler/src/provider_analyzer.ts index df3248956b..20cc773ad9 100644 --- a/modules/@angular/compiler/src/provider_analyzer.ts +++ b/modules/@angular/compiler/src/provider_analyzer.ts @@ -9,19 +9,24 @@ import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveSummary, CompileNgModuleMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileTokenMetadata, CompileTypeMetadata, tokenName, tokenReference} from './compile_metadata'; import {isBlank, isPresent} from './facade/lang'; -import {Identifiers, resolveIdentifier} from './identifiers'; +import {Identifiers, createIdentifierToken, resolveIdentifier} from './identifiers'; import {ParseError, ParseSourceSpan} from './parse_util'; -import {AttrAst, DirectiveAst, ProviderAst, ProviderAstType, ReferenceAst} from './template_parser/template_ast'; +import {AttrAst, DirectiveAst, ProviderAst, ProviderAstType, QueryId, QueryMatch, ReferenceAst} from './template_parser/template_ast'; export class ProviderError extends ParseError { constructor(message: string, span: ParseSourceSpan) { super(span, message); } } +export interface QueryWithId { + meta: CompileQueryMetadata; + id: QueryId; +} + export class ProviderViewContext { /** * @internal */ - viewQueries: Map; + viewQueries: Map; /** * @internal */ @@ -40,13 +45,14 @@ export class ProviderViewContext { } export class ProviderElementContext { - private _contentQueries: Map; + private _contentQueries: Map; private _transformedProviders = new Map(); private _seenProviders = new Map(); private _allProviders: Map; private _attrs: {[key: string]: string}; private _hasViewContainer: boolean = false; + private _queriedTokens = new Map(); constructor( public viewContext: ProviderViewContext, private _parent: ProviderElementContext, @@ -57,19 +63,21 @@ export class ProviderElementContext { const directivesMeta = _directiveAsts.map(directiveAst => directiveAst.directive); this._allProviders = _resolveProvidersFromDirectives(directivesMeta, _sourceSpan, viewContext.errors); - this._contentQueries = _getContentQueries(directivesMeta); - const queriedTokens = new Map(); + this._contentQueries = _getContentQueries(this.depth, directivesMeta); Array.from(this._allProviders.values()).forEach((provider) => { - this._addQueryReadsTo(provider.token, queriedTokens); + this._addQueryReadsTo(provider.token, provider.token, this._queriedTokens); }); - refs.forEach((refAst) => { this._addQueryReadsTo({value: refAst.name}, queriedTokens); }); - if (isPresent(queriedTokens.get(resolveIdentifier(Identifiers.ViewContainerRef)))) { + refs.forEach((refAst) => { + let defaultQueryValue = refAst.value || createIdentifierToken(Identifiers.ElementRef); + this._addQueryReadsTo({value: refAst.name}, defaultQueryValue, this._queriedTokens); + }); + if (this._queriedTokens.get(resolveIdentifier(Identifiers.ViewContainerRef))) { this._hasViewContainer = true; } // create the providers that we know are eager first Array.from(this._allProviders.values()).forEach((provider) => { - const eager = provider.eager || isPresent(queriedTokens.get(tokenReference(provider.token))); + const eager = provider.eager || this._queriedTokens.get(tokenReference(provider.token)); if (eager) { this._getOrCreateLocalProvider(provider.providerType, provider.token, true); } @@ -83,6 +91,16 @@ export class ProviderElementContext { }); } + get depth(): number { + let d = 0; + let current: ProviderElementContext = this; + while (current._parent) { + d++; + current = current._parent; + } + return d; + } + get transformProviders(): ProviderAst[] { return Array.from(this._transformedProviders.values()); } @@ -98,24 +116,36 @@ export class ProviderElementContext { get transformedHasViewContainer(): boolean { return this._hasViewContainer; } - private _addQueryReadsTo(token: CompileTokenMetadata, queryReadTokens: Map) { + get queryMatches(): QueryMatch[] { + const allMatches: QueryMatch[] = []; + this._queriedTokens.forEach((matches: QueryMatch[]) => { allMatches.push(...matches); }); + return allMatches; + } + + private _addQueryReadsTo( + token: CompileTokenMetadata, defaultValue: CompileTokenMetadata, + queryReadTokens: Map) { this._getQueriesFor(token).forEach((query) => { - const queryReadToken = query.read || token; - if (isBlank(queryReadTokens.get(tokenReference(queryReadToken)))) { - queryReadTokens.set(tokenReference(queryReadToken), true); + const queryValue = query.meta.read || defaultValue; + const tokenRef = tokenReference(queryValue); + let queryMatches = queryReadTokens.get(tokenRef); + if (!queryMatches) { + queryMatches = []; + queryReadTokens.set(tokenRef, queryMatches); } + queryMatches.push({query: query.id, value: queryValue}); }); } - private _getQueriesFor(token: CompileTokenMetadata): CompileQueryMetadata[] { - const result: CompileQueryMetadata[] = []; + private _getQueriesFor(token: CompileTokenMetadata): QueryWithId[] { + const result: QueryWithId[] = []; let currentEl: ProviderElementContext = this; let distance = 0; - let queries: CompileQueryMetadata[]; + let queries: QueryWithId[]; while (currentEl !== null) { queries = currentEl._contentQueries.get(tokenReference(token)); if (queries) { - result.push(...queries.filter((query) => query.descendants || distance <= 1)); + result.push(...queries.filter((query) => query.meta.descendants || distance <= 1)); } if (currentEl._directiveAsts.length > 0) { distance++; @@ -452,27 +482,32 @@ function _resolveProviders( } -function _getViewQueries(component: CompileDirectiveMetadata): Map { - const viewQueries = new Map(); +function _getViewQueries(component: CompileDirectiveMetadata): Map { + const viewQueries = new Map(); if (component.viewQueries) { - component.viewQueries.forEach((query) => _addQueryToTokenMap(viewQueries, query)); + component.viewQueries.forEach( + (query, queryIndex) => _addQueryToTokenMap( + viewQueries, + {meta: query, id: {elementDepth: null, directiveIndex: null, queryIndex: queryIndex}})); } return viewQueries; } -function _getContentQueries(directives: CompileDirectiveSummary[]): - Map { - const contentQueries = new Map(); - directives.forEach(directive => { +function _getContentQueries( + elementDepth: number, directives: CompileDirectiveSummary[]): Map { + const contentQueries = new Map(); + directives.forEach((directive, directiveIndex) => { if (directive.queries) { - directive.queries.forEach((query) => _addQueryToTokenMap(contentQueries, query)); + directive.queries.forEach( + (query, queryIndex) => _addQueryToTokenMap( + contentQueries, {meta: query, id: {elementDepth, directiveIndex, queryIndex}})); } }); return contentQueries; } -function _addQueryToTokenMap(map: Map, query: CompileQueryMetadata) { - query.selectors.forEach((token: CompileTokenMetadata) => { +function _addQueryToTokenMap(map: Map, query: QueryWithId) { + query.meta.selectors.forEach((token: CompileTokenMetadata) => { let entry = map.get(tokenReference(token)); if (!entry) { entry = []; diff --git a/modules/@angular/compiler/src/template_parser/template_ast.ts b/modules/@angular/compiler/src/template_parser/template_ast.ts index a0d964345f..e3acdf4e55 100644 --- a/modules/@angular/compiler/src/template_parser/template_ast.ts +++ b/modules/@angular/compiler/src/template_parser/template_ast.ts @@ -126,9 +126,9 @@ export class ElementAst implements TemplateAst { public name: string, public attrs: AttrAst[], public inputs: BoundElementPropertyAst[], public outputs: BoundEventAst[], public references: ReferenceAst[], public directives: DirectiveAst[], public providers: ProviderAst[], - public hasViewContainer: boolean, public children: TemplateAst[], - public ngContentIndex: number, public sourceSpan: ParseSourceSpan, - public endSourceSpan: ParseSourceSpan) {} + public hasViewContainer: boolean, public queryMatches: QueryMatch[], + public children: TemplateAst[], public ngContentIndex: number, + public sourceSpan: ParseSourceSpan, public endSourceSpan: ParseSourceSpan) {} visit(visitor: TemplateAstVisitor, context: any): any { return visitor.visitElement(this, context); @@ -143,8 +143,8 @@ export class EmbeddedTemplateAst implements TemplateAst { public attrs: AttrAst[], public outputs: BoundEventAst[], public references: ReferenceAst[], public variables: VariableAst[], public directives: DirectiveAst[], public providers: ProviderAst[], public hasViewContainer: boolean, - public children: TemplateAst[], public ngContentIndex: number, - public sourceSpan: ParseSourceSpan) {} + public queryMatches: QueryMatch[], public children: TemplateAst[], + public ngContentIndex: number, public sourceSpan: ParseSourceSpan) {} visit(visitor: TemplateAstVisitor, context: any): any { return visitor.visitEmbeddedTemplate(this, context); @@ -241,6 +241,20 @@ export enum PropertyBindingType { Animation } +/** + * This id differentiates a query on an element from any query on any child. + */ +export interface QueryId { + elementDepth: number; + directiveIndex: number; + queryIndex: number; +} + +export interface QueryMatch { + query: QueryId; + value: CompileTokenMetadata; +} + /** * A visitor for {@link TemplateAst} trees that will process each node. */ diff --git a/modules/@angular/compiler/src/template_parser/template_parser.ts b/modules/@angular/compiler/src/template_parser/template_parser.ts index 4927af67e8..a3889dd279 100644 --- a/modules/@angular/compiler/src/template_parser/template_parser.ts +++ b/modules/@angular/compiler/src/template_parser/template_parser.ts @@ -341,8 +341,9 @@ class TemplateParseVisitor implements html.Visitor { parsedElement = new EmbeddedTemplateAst( attrs, events, references, elementVars, providerContext.transformedDirectiveAsts, - providerContext.transformProviders, providerContext.transformedHasViewContainer, children, - hasInlineTemplates ? null : ngContentIndex, element.sourceSpan); + providerContext.transformProviders, providerContext.transformedHasViewContainer, + providerContext.queryMatches, children, hasInlineTemplates ? null : ngContentIndex, + element.sourceSpan); } else { this._assertElementExists(matchElement, element); this._assertOnlyOneComponent(directiveAsts, element.sourceSpan); @@ -352,7 +353,7 @@ class TemplateParseVisitor implements html.Visitor { parsedElement = new ElementAst( nodeName, attrs, elementProps, events, references, providerContext.transformedDirectiveAsts, providerContext.transformProviders, - providerContext.transformedHasViewContainer, children, + providerContext.transformedHasViewContainer, providerContext.queryMatches, children, hasInlineTemplates ? null : ngContentIndex, element.sourceSpan, element.endSourceSpan); this._findComponentDirectives(directiveAsts) @@ -386,8 +387,8 @@ class TemplateParseVisitor implements html.Visitor { parsedElement = new EmbeddedTemplateAst( [], [], [], templateElementVars, templateProviderContext.transformedDirectiveAsts, templateProviderContext.transformProviders, - templateProviderContext.transformedHasViewContainer, [parsedElement], ngContentIndex, - element.sourceSpan); + templateProviderContext.transformedHasViewContainer, templateProviderContext.queryMatches, + [parsedElement], ngContentIndex, element.sourceSpan); } return parsedElement; @@ -755,7 +756,7 @@ class NonBindableVisitor implements html.Visitor { const ngContentIndex = parent.findNgContentIndex(selector); const children = html.visitAll(this, ast.children, EMPTY_ELEMENT_CONTEXT); return new ElementAst( - ast.name, html.visitAll(this, ast.attrs), [], [], [], [], [], false, children, + ast.name, html.visitAll(this, ast.attrs), [], [], [], [], [], false, [], children, ngContentIndex, ast.sourceSpan, ast.endSourceSpan); } visitComment(comment: html.Comment, context: any): any { return null; } diff --git a/modules/@angular/compiler/src/view_compiler/event_binder.ts b/modules/@angular/compiler/src/view_compiler/event_binder.ts index 57ffd9584d..6fa54fb43c 100644 --- a/modules/@angular/compiler/src/view_compiler/event_binder.ts +++ b/modules/@angular/compiler/src/view_compiler/event_binder.ts @@ -105,8 +105,8 @@ function generateHandleEventMethod( compileElement.view, compileElement.view, compileElement.view.componentContext, renderEvent.handler, `sub_${renderEventIdx}`); const trueStmts = evalResult.stmts; - if (evalResult.preventDefault) { - trueStmts.push(resultVar.set(evalResult.preventDefault.and(resultVar)).toStmt()); + if (evalResult.allowDefault) { + trueStmts.push(resultVar.set(evalResult.allowDefault.and(resultVar)).toStmt()); } // TODO(tbosch): convert this into a `switch` once our OutputAst supports it. handleEventStmts.push( diff --git a/modules/@angular/compiler/src/view_compiler_next/README.md b/modules/@angular/compiler/src/view_compiler_next/README.md new file mode 100644 index 0000000000..cf1157a073 --- /dev/null +++ b/modules/@angular/compiler/src/view_compiler_next/README.md @@ -0,0 +1,5 @@ +# Compiler Integration for new View Engine + +This folder contains the code to integrate the compiler with the new view engine. + +Note: This is work in progress, and once complete will replace the regular view_compiler folder. \ No newline at end of file diff --git a/modules/@angular/compiler/src/view_compiler_next/view_compiler.ts b/modules/@angular/compiler/src/view_compiler_next/view_compiler.ts new file mode 100644 index 0000000000..90889f4379 --- /dev/null +++ b/modules/@angular/compiler/src/view_compiler_next/view_compiler.ts @@ -0,0 +1,738 @@ +/** + * @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 {ChangeDetectionStrategy} from '@angular/core'; + +import {AnimationEntryCompileResult} from '../animation/animation_compiler'; +import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeSummary, CompileProviderMetadata, CompileTokenMetadata, identifierModuleUrl, identifierName, tokenReference} from '../compile_metadata'; +import {EventHandlerVars, NameResolver, convertActionBinding, convertPropertyBinding} from '../compiler_util/expression_converter'; +import {CompilerConfig} from '../config'; +import {AST, ASTWithSource, Interpolation} from '../expression_parser/ast'; +import {Identifiers, createIdentifier, resolveIdentifier} from '../identifiers'; +import {CompilerInjectable} from '../injectable'; +import * as o from '../output/output_ast'; +import {convertValueToOutputAst} from '../output/value_util'; +import {LifecycleHooks, viewEngine} from '../private_import_core'; +import {ElementSchemaRegistry} from '../schema/element_schema_registry'; +import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ProviderAst, ProviderAstType, QueryId, QueryMatch, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '../template_parser/template_ast'; +import {ViewEncapsulationEnum} from '../view_compiler/constants'; +import {ComponentFactoryDependency, ComponentViewDependency, DirectiveWrapperDependency, ViewCompileResult, ViewCompiler} from '../view_compiler/view_compiler'; + +const CLASS_ATTR = 'class'; +const STYLE_ATTR = 'style'; + +@CompilerInjectable() +export class ViewCompilerNext extends ViewCompiler { + constructor( + private _genConfigNext: CompilerConfig, private _schemaRegistryNext: ElementSchemaRegistry) { + super(_genConfigNext, _schemaRegistryNext); + } + + compileComponent( + component: CompileDirectiveMetadata, template: TemplateAst[], styles: o.Expression, + pipes: CompilePipeSummary[], + compiledAnimations: AnimationEntryCompileResult[]): ViewCompileResult { + const compName = identifierName(component.type) + (component.isHost ? `_Host` : ''); + + let embeddedViewCount = 0; + + const viewBuilderFactory = (parent: ViewBuilder): ViewBuilder => { + const embeddedViewIndex = embeddedViewCount++; + const viewName = `view_${compName}_${embeddedViewIndex}`; + return new ViewBuilder(parent, viewName, viewBuilderFactory); + }; + + const visitor = viewBuilderFactory(null); + visitor.visitAll([], template, 0); + + const statements: o.Statement[] = []; + statements.push(...visitor.build(component)); + + return new ViewCompileResult(statements, visitor.viewName, []); + } +} + +interface ViewBuilderFactory { + (parent: ViewBuilder): ViewBuilder; +} + +interface UpdateExpression { + nodeIndex: number; + expressions: {context: o.Expression, value: AST}[]; +} + +interface HandleEventExpression { + nodeIndex: number; + context: o.Expression; + eventName: string; + expression: AST; +} + +const VIEW_VAR = o.variable('view'); +const CHECK_VAR = o.variable('check'); +const COMP_VAR = o.variable('comp'); +const NODE_INDEX_VAR = o.variable('nodeIndex'); +const EVENT_NAME_VAR = o.variable('eventName'); +const ALLOW_DEFAULT_VAR = o.variable(`allowDefault`); + +class ViewBuilder implements TemplateAstVisitor, NameResolver { + private nodeDefs: o.Expression[] = []; + private refNodeIndices: {[refName: string]: number} = {}; + private variables: VariableAst[] = []; + private children: ViewBuilder[] = []; + private updateExpressions: UpdateExpression[] = []; + private handleEventExpressions: HandleEventExpression[] = []; + + constructor( + private parent: ViewBuilder, public viewName: string, + private viewBuilderFactory: ViewBuilderFactory) {} + + visitAll(variables: VariableAst[], astNodes: TemplateAst[], elementDepth: number) { + this.variables = variables; + templateVisitAll(this, astNodes, {elementDepth}); + if (astNodes.length === 0 || (this.parent && hasViewContainer(astNodes[astNodes.length - 1]))) { + // if the view is empty, or an embedded view has a view container as last root nde, + // create an additional root node. + this.nodeDefs.push(o.importExpr(createIdentifier(Identifiers.anchorDef)).callFn([ + o.literal(viewEngine.NodeFlags.None), o.NULL_EXPR, o.NULL_EXPR, o.literal(0) + ])); + } + } + + build(component: CompileDirectiveMetadata, targetStatements: o.Statement[] = []): o.Statement[] { + const compType = o.importType(component.type); + this.children.forEach((child) => { child.build(component, targetStatements); }); + + const updateStmts: o.Statement[] = []; + let updateBindingCount = 0; + this.updateExpressions + .forEach( + ({expressions, nodeIndex}) => { + const exprs = expressions.map(({context, value}) => { + const bindingId = `${updateBindingCount++}`; + const {stmts, currValExpr} = + convertPropertyBinding(null, this, context, value, bindingId); + updateStmts.push(...stmts); + return currValExpr; + }); + if (exprs.length > 10) { + updateStmts.push( + CHECK_VAR + .callFn([ + VIEW_VAR, o.literal(nodeIndex), + o.literal(viewEngine.ArgumentType.Dynamic), o.literalArr(exprs) + ]) + .toStmt()); + } else { + updateStmts.push( + CHECK_VAR.callFn(([VIEW_VAR, o.literal(nodeIndex), o.literal(viewEngine.ArgumentType.Inline)]).concat(exprs)).toStmt()); + } + }); + let updateFn: o.Expression; + if (updateStmts.length > 0) { + updateFn = o.fn( + [new o.FnParam(CHECK_VAR.name), new o.FnParam(VIEW_VAR.name)], + [COMP_VAR.set(VIEW_VAR.prop('component')).toDeclStmt(compType), ...updateStmts]); + } else { + updateFn = o.NULL_EXPR; + } + + const handleEventStmts: o.Statement[] = []; + let handleEventBindingCount = 0; + this.handleEventExpressions.forEach(({expression, context, nodeIndex, eventName}) => { + const bindingId = `${handleEventBindingCount++}`; + const {stmts, allowDefault} = + convertActionBinding(null, this, context, expression, bindingId); + const trueStmts = stmts; + if (allowDefault) { + trueStmts.push(ALLOW_DEFAULT_VAR.set(allowDefault.and(ALLOW_DEFAULT_VAR)).toStmt()); + } + handleEventStmts.push(new o.IfStmt( + o.literal(nodeIndex) + .identical(NODE_INDEX_VAR) + .and(o.literal(eventName).identical(EVENT_NAME_VAR)), + trueStmts)); + }); + let handleEventFn: o.Expression; + if (handleEventStmts.length > 0) { + handleEventFn = o.fn( + [ + new o.FnParam(VIEW_VAR.name), new o.FnParam(NODE_INDEX_VAR.name), + new o.FnParam(EVENT_NAME_VAR.name), new o.FnParam(EventHandlerVars.event.name) + ], + [ + ALLOW_DEFAULT_VAR.set(o.literal(true)).toDeclStmt(o.BOOL_TYPE), + COMP_VAR.set(VIEW_VAR.prop('component')).toDeclStmt(compType), ...handleEventStmts, + new o.ReturnStatement(ALLOW_DEFAULT_VAR) + ]); + } else { + handleEventFn = o.NULL_EXPR; + } + + let viewFlags = viewEngine.ViewFlags.None; + if (!this.parent && component.changeDetection === ChangeDetectionStrategy.OnPush) { + viewFlags |= viewEngine.ViewFlags.OnPush; + } + const viewFactory = new o.DeclareFunctionStmt( + this.viewName, [], + [new o.ReturnStatement(o.importExpr(createIdentifier(Identifiers.viewDef)).callFn([ + o.literal(viewFlags), o.literalArr(this.nodeDefs), updateFn, handleEventFn + ]))]); + + targetStatements.push(viewFactory); + return targetStatements; + } + + visitNgContent(ast: NgContentAst, context: any): any {} + + visitText(ast: TextAst, context: any): any { + // textDef(ngContentIndex: number, constants: string[]): NodeDef; + this.nodeDefs.push(o.importExpr(createIdentifier(Identifiers.textDef)).callFn([ + o.NULL_EXPR, o.literalArr([o.literal(ast.value)]) + ])); + } + + visitBoundText(ast: BoundTextAst, context: any): any { + const nodeIndex = this.nodeDefs.length; + const astWithSource = ast.value; + const inter = astWithSource.ast; + this.updateExpressions.push({ + nodeIndex, + expressions: inter.expressions.map((expr) => { return {context: COMP_VAR, value: expr}; }) + }); + + // textDef(ngContentIndex: number, constants: string[]): NodeDef; + this.nodeDefs.push(o.importExpr(createIdentifier(Identifiers.textDef)).callFn([ + o.NULL_EXPR, o.literalArr(inter.strings.map(s => o.literal(s))) + ])); + } + + visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: {elementDepth: number}): any { + const nodeIndex = this.nodeDefs.length; + // reserve the space in the nodeDefs array + this.nodeDefs.push(null); + + const {flags, queryMatchesExpr} = this._visitElementOrTemplate(nodeIndex, ast, context); + + const childCount = this.nodeDefs.length - nodeIndex - 1; + const childVisitor = this.viewBuilderFactory(this); + this.children.push(childVisitor); + childVisitor.visitAll(ast.variables, ast.children, context.elementDepth + 1); + + // anchorDef( + // flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number, + // childCount: number, templateFactory?: ViewDefinitionFactory): NodeDef; + this.nodeDefs[nodeIndex] = o.importExpr(createIdentifier(Identifiers.anchorDef)).callFn([ + o.literal(flags), queryMatchesExpr, o.NULL_EXPR, o.literal(childCount), + o.variable(childVisitor.viewName) + ]); + } + + visitElement(ast: ElementAst, context: {elementDepth: number}): any { + const nodeIndex = this.nodeDefs.length; + // reserve the space in the nodeDefs array so we can add children + this.nodeDefs.push(null); + + const {flags, usedEvents, queryMatchesExpr, hostBindings} = + this._visitElementOrTemplate(nodeIndex, ast, context); + + templateVisitAll(this, ast.children, {elementDepth: context.elementDepth + 1}); + + const childCount = this.nodeDefs.length - nodeIndex - 1; + + ast.inputs.forEach((inputAst) => { + hostBindings.push({context: COMP_VAR, value: (inputAst.value).ast}); + }); + this.updateExpressions.push({nodeIndex, expressions: hostBindings}); + + const inputDefs = elementBindingDefs(ast.inputs); + ast.directives.forEach( + (dirAst, dirIndex) => { inputDefs.push(...elementBindingDefs(dirAst.hostProperties)); }); + const outputDefs = usedEvents.map(([target, eventName]) => { + return target ? o.literalArr([o.literal(target), o.literal(eventName)]) : + o.literal(eventName); + }); + + // elementDef( + // flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number, + // childCount: number, name: string, fixedAttrs: {[name: string]: string} = {}, + // bindings?: + // ([BindingType.ElementClass, string] | [BindingType.ElementStyle, string, string] | + // [BindingType.ElementAttribute | BindingType.ElementProperty, string, + // SecurityContext])[], + // outputs?: (string | [string, string])[]): NodeDef; + this.nodeDefs[nodeIndex] = o.importExpr(createIdentifier(Identifiers.elementDef)).callFn([ + o.literal(flags), queryMatchesExpr, o.NULL_EXPR, o.literal(childCount), o.literal(ast.name), + fixedAttrsDef(ast), inputDefs.length ? o.literalArr(inputDefs) : o.NULL_EXPR, + outputDefs.length ? o.literalArr(outputDefs) : o.NULL_EXPR + ]); + } + + private _visitElementOrTemplate( + nodeIndex: number, ast: { + hasViewContainer: boolean, + outputs: BoundEventAst[], + directives: DirectiveAst[], + providers: ProviderAst[], + references: ReferenceAst[], + queryMatches: QueryMatch[] + }, + context: {elementDepth: number}): { + flags: number, + usedEvents: [string, string][], + queryMatchesExpr: o.Expression, + hostBindings: {value: AST, context: o.Expression}[], + } { + let flags = viewEngine.NodeFlags.None; + if (ast.hasViewContainer) { + flags |= viewEngine.NodeFlags.HasEmbeddedViews; + } + const usedEvents = new Map(); + ast.outputs.forEach((event) => { + usedEvents.set( + viewEngine.elementEventFullName(event.target, event.name), [event.target, event.name]); + }); + ast.directives.forEach((dirAst) => { + dirAst.hostEvents.forEach((event) => { + usedEvents.set( + viewEngine.elementEventFullName(event.target, event.name), [event.target, event.name]); + }); + }); + const hostBindings: {value: AST, context: o.Expression}[] = []; + const hostEvents: {context: o.Expression, eventAst: BoundEventAst}[] = []; + ast.providers.forEach((providerAst, providerIndex) => { + let dirAst: DirectiveAst; + let dirIndex: number; + ast.directives.forEach((localDirAst, i) => { + if (localDirAst.directive.type.reference === providerAst.token.identifier.reference) { + dirAst = localDirAst; + dirIndex = i; + } + }); + if (dirAst) { + const {hostBindings: dirHostBindings, hostEvents: dirHostEvents} = this._visitDirective( + providerAst, dirAst, dirIndex, nodeIndex, context.elementDepth, ast.references, + ast.queryMatches, usedEvents); + hostBindings.push(...dirHostBindings); + hostEvents.push(...dirHostEvents); + } else { + this._visitProvider(providerAst, ast.queryMatches); + } + }); + + let queryMatchExprs: o.Expression[] = []; + ast.queryMatches.forEach((match) => { + let valueType: number; + if (tokenReference(match.value) === resolveIdentifier(Identifiers.ElementRef)) { + valueType = viewEngine.QueryValueType.ElementRef; + } else if (tokenReference(match.value) === resolveIdentifier(Identifiers.ViewContainerRef)) { + valueType = viewEngine.QueryValueType.ViewContainerRef; + } else if (tokenReference(match.value) === resolveIdentifier(Identifiers.TemplateRef)) { + valueType = viewEngine.QueryValueType.TemplateRef; + } + if (valueType != null) { + queryMatchExprs.push( + o.literalArr([o.literal(calcQueryId(match.query)), o.literal(valueType)])); + } + }); + ast.references.forEach((ref) => { + let valueType: number; + if (!ref.value) { + valueType = viewEngine.QueryValueType.RenderElement; + } else if (tokenReference(ref.value) === resolveIdentifier(Identifiers.TemplateRef)) { + valueType = viewEngine.QueryValueType.TemplateRef; + } + if (valueType != null) { + this.refNodeIndices[ref.name] = nodeIndex; + queryMatchExprs.push(o.literalArr([o.literal(`#${ref.name}`), o.literal(valueType)])); + } + }); + ast.outputs.forEach( + (outputAst) => { hostEvents.push({context: COMP_VAR, eventAst: outputAst}); }); + hostEvents.forEach((hostEvent) => { + this.handleEventExpressions.push({ + nodeIndex, + context: hostEvent.context, + eventName: + viewEngine.elementEventFullName(hostEvent.eventAst.target, hostEvent.eventAst.name), + expression: (hostEvent.eventAst.handler).ast + }); + }); + + return { + flags, + usedEvents: Array.from(usedEvents.values()), + queryMatchesExpr: queryMatchExprs.length ? o.literalArr(queryMatchExprs) : o.NULL_EXPR, + hostBindings, + }; + } + + private _visitDirective( + providerAst: ProviderAst, directiveAst: DirectiveAst, directiveIndex: number, + elementNodeIndex: number, elementDepth: number, refs: ReferenceAst[], + queryMatches: QueryMatch[], usedEvents: Map): { + hostBindings: {value: AST, context: o.Expression}[], + hostEvents: {context: o.Expression, eventAst: BoundEventAst}[] + } { + const nodeIndex = this.nodeDefs.length; + // reserve the space in the nodeDefs array so we can add children + this.nodeDefs.push(null); + + const {flags, queryMatchExprs, providerExpr, providerType, depsExpr} = + this._visitProviderOrDirective(providerAst, queryMatches); + + refs.forEach((ref) => { + if (ref.value && tokenReference(ref.value) === tokenReference(providerAst.token)) { + this.refNodeIndices[ref.name] = nodeIndex; + queryMatchExprs.push(o.literalArr( + [o.literal(`#${ref.name}`), o.literal(viewEngine.QueryValueType.Provider)])); + } + }); + + let compView = o.NULL_EXPR; + if (directiveAst.directive.isComponent) { + compView = o.importExpr({reference: directiveAst.directive.componentViewType}); + } + + const inputDefs = directiveAst.inputs.map((inputAst, inputIndex) => { + const mapValue = o.literalArr([o.literal(inputIndex), o.literal(inputAst.directiveName)]); + // Note: it's important to not quote the key so that we can capture renames by minifiers! + return new o.LiteralMapEntry(inputAst.directiveName, mapValue, false); + }); + + const outputDefs: o.LiteralMapEntry[] = []; + const dirMeta = directiveAst.directive; + Object.keys(dirMeta.outputs).forEach((propName) => { + const eventName = dirMeta.outputs[propName]; + if (usedEvents.has(eventName)) { + // Note: it's important to not quote the key so that we can capture renames by minifiers! + outputDefs.push(new o.LiteralMapEntry(propName, o.literal(eventName), false)); + } + }); + if (directiveAst.inputs.length) { + this.updateExpressions.push({ + nodeIndex, + expressions: directiveAst.inputs.map( + input => { return {context: COMP_VAR, value: (input.value).ast}; }) + }); + } + + const dirContextExpr = o.importExpr(createIdentifier(Identifiers.nodeValue)).callFn([ + VIEW_VAR, o.literal(nodeIndex) + ]); + const hostBindings = directiveAst.hostProperties.map((hostBindingAst) => { + return { + value: (hostBindingAst.value).ast, + context: dirContextExpr, + }; + }); + const hostEvents = directiveAst.hostEvents.map( + (hostEventAst) => { return {context: dirContextExpr, eventAst: hostEventAst}; }); + + const childCount = directiveAst.directive.queries.length; + directiveAst.directive.queries.forEach((query, queryIndex) => { + const queryId: QueryId = {elementDepth, directiveIndex, queryIndex}; + const bindingType = + query.first ? viewEngine.QueryBindingType.First : viewEngine.QueryBindingType.All; + // queryDef( + // flags: NodeFlags, id: string, bindings: {[propName: string]: QueryBindingType}): NodeDef + // { + this.nodeDefs.push(o.importExpr(createIdentifier(Identifiers.queryDef)).callFn([ + o.literal(viewEngine.NodeFlags.HasContentQuery), o.literal(calcQueryId(queryId)), + new o.LiteralMapExpr([new o.LiteralMapEntry(query.propertyName, o.literal(bindingType))]) + ])); + }); + + // directiveDef( + // flags: NodeFlags, matchedQueries: [string, QueryValueType][], childCount: number, ctor: + // any, + // deps: ([DepFlags, any] | any)[], props?: {[name: string]: [number, string]}, + // outputs?: {[name: string]: string}, component?: () => ViewDefinition): NodeDef; + const nodeDef = o.importExpr(createIdentifier(Identifiers.directiveDef)).callFn([ + o.literal(flags), queryMatchExprs.length ? o.literalArr(queryMatchExprs) : o.NULL_EXPR, + o.literal(childCount), providerExpr, depsExpr, + inputDefs.length ? new o.LiteralMapExpr(inputDefs) : o.NULL_EXPR, + outputDefs.length ? new o.LiteralMapExpr(outputDefs) : o.NULL_EXPR, compView + ]); + this.nodeDefs[nodeIndex] = nodeDef; + + return {hostBindings, hostEvents}; + } + + private _visitProvider(providerAst: ProviderAst, queryMatches: QueryMatch[]): void { + const nodeIndex = this.nodeDefs.length; + // reserve the space in the nodeDefs array so we can add children + this.nodeDefs.push(null); + + const {flags, queryMatchExprs, providerExpr, providerType, depsExpr} = + this._visitProviderOrDirective(providerAst, queryMatches); + + // providerDef( + // flags: NodeFlags, matchedQueries: [string, QueryValueType][], type: ProviderType, token: + // any, + // value: any, deps: ([DepFlags, any] | any)[]): NodeDef; + const nodeDef = o.importExpr(createIdentifier(Identifiers.providerDef)).callFn([ + o.literal(flags), queryMatchExprs.length ? o.literalArr(queryMatchExprs) : o.NULL_EXPR, + o.literal(providerType), tokenExpr(providerAst.token), providerExpr, depsExpr + ]); + this.nodeDefs[nodeIndex] = nodeDef; + } + + private _visitProviderOrDirective(providerAst: ProviderAst, queryMatches: QueryMatch[]): { + flags: number, + queryMatchExprs: o.Expression[], + providerExpr: o.Expression, + providerType: number, + depsExpr: o.Expression + } { + let flags = viewEngine.NodeFlags.None; + if (!providerAst.eager) { + flags |= viewEngine.NodeFlags.LazyProvider; + } + providerAst.lifecycleHooks.forEach((lifecycleHook) => { + // for regular providers, we only support ngOnDestroy + if (lifecycleHook === LifecycleHooks.OnDestroy || + providerAst.providerType === ProviderAstType.Directive || + providerAst.providerType === ProviderAstType.Component) { + flags |= lifecycleHookToNodeFlag(lifecycleHook); + } + }); + let queryMatchExprs: o.Expression[] = []; + + queryMatches.forEach((match) => { + if (tokenReference(match.value) === tokenReference(providerAst.token)) { + queryMatchExprs.push(o.literalArr( + [o.literal(calcQueryId(match.query)), o.literal(viewEngine.QueryValueType.Provider)])); + } + }); + const {providerExpr, providerType, depsExpr} = providerDef(providerAst); + return {flags, queryMatchExprs, providerExpr, providerType, depsExpr}; + } + + callPipe(name: string, input: o.Expression, args: o.Expression[]): o.Expression { + throw new Error('Pipes are not yet supported!'); + } + + getLocal(name: string): o.Expression { + if (name == EventHandlerVars.event.name) { + return EventHandlerVars.event; + } + let currViewExpr: o.Expression = VIEW_VAR; + for (let currBuilder: ViewBuilder = this; currBuilder; + currBuilder = currBuilder.parent, currViewExpr = currViewExpr.prop('parent')) { + // check references + const refNodeIndex = currBuilder.refNodeIndices[name]; + if (refNodeIndex != null) { + return o.importExpr(createIdentifier(Identifiers.nodeValue)).callFn([ + currViewExpr, o.literal(refNodeIndex) + ]); + } + + // check variables + const varAst = currBuilder.variables.find((varAst) => varAst.name === name); + if (varAst) { + return currViewExpr.prop('context').prop(varAst.value); + } + } + return null; + } + + visitDirective(ast: DirectiveAst, context: {usedEvents: Set}): any {} + visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any {} + visitReference(ast: ReferenceAst, context: any): any {} + visitVariable(ast: VariableAst, context: any): any {} + visitEvent(ast: BoundEventAst, context: any): any {} + visitElementProperty(ast: BoundElementPropertyAst, context: any): any {} + visitAttr(ast: AttrAst, context: any): any {} +} + +function providerDef(providerAst: ProviderAst): + {providerExpr: o.Expression, providerType: number, depsExpr: o.Expression} { + return providerAst.multiProvider ? multiProviderDef(providerAst.providers) : + singleProviderDef(providerAst.providers[0]); +} + +function multiProviderDef(providers: CompileProviderMetadata[]): + {providerExpr: o.Expression, providerType: number, depsExpr: o.Expression} { + const allDepDefs: o.Expression[] = []; + const allParams: o.FnParam[] = []; + const exprs = providers.map((provider, providerIndex) => { + const depExprs = provider.deps.map((dep, depIndex) => { + const paramName = `p${providerIndex}_${depIndex}`; + allParams.push(new o.FnParam(paramName, o.DYNAMIC_TYPE)); + allDepDefs.push(depDef(dep)); + return o.variable(paramName); + }); + let expr: o.Expression; + if (provider.useClass) { + expr = o.importExpr(provider.useClass).instantiate(depExprs); + } else if (provider.useFactory) { + expr = o.importExpr(provider.useFactory).callFn(depExprs); + } else if (provider.useExisting) { + expr = depExprs[0]; + } else { + expr = convertValueToOutputAst(provider.useValue); + } + return expr; + }); + const providerExpr = o.fn(allParams, [new o.ReturnStatement(o.literalArr(exprs))]); + return { + providerExpr, + providerType: viewEngine.ProviderType.Factory, + depsExpr: o.literalArr(allDepDefs) + }; +} + +function singleProviderDef(providerMeta: CompileProviderMetadata): + {providerExpr: o.Expression, providerType: number, depsExpr: o.Expression} { + let providerExpr: o.Expression; + let providerType: number; + let deps: CompileDiDependencyMetadata[]; + if (providerMeta.useClass) { + providerExpr = o.importExpr(providerMeta.useClass); + providerType = viewEngine.ProviderType.Class; + deps = providerMeta.deps || providerMeta.useClass.diDeps; + } else if (providerMeta.useFactory) { + providerExpr = o.importExpr(providerMeta.useFactory); + providerType = viewEngine.ProviderType.Factory; + deps = providerMeta.deps || providerMeta.useFactory.diDeps; + } else if (providerMeta.useExisting) { + providerExpr = o.NULL_EXPR; + providerType = viewEngine.ProviderType.UseExisting; + deps = [{token: providerMeta.useExisting}]; + } else { + providerExpr = convertValueToOutputAst(providerMeta.useValue); + providerType = viewEngine.ProviderType.Value; + deps = []; + } + const depsExpr = o.literalArr(deps.map(dep => depDef(dep))); + return {providerExpr, providerType, depsExpr}; +} + +function tokenExpr(tokenMeta: CompileTokenMetadata): o.Expression { + return tokenMeta.identifier ? o.importExpr(tokenMeta.identifier) : o.literal(tokenMeta.value); +} + +function depDef(dep: CompileDiDependencyMetadata): o.Expression { + // Note: the following fields have already been normalized out by provider_analyzer: + // - isAttribute, isSelf, isHost + const expr = dep.isValue ? convertValueToOutputAst(dep.value) : tokenExpr(dep.token); + let flags = viewEngine.DepFlags.None; + if (dep.isSkipSelf) { + flags |= viewEngine.DepFlags.SkipSelf; + } + if (dep.isOptional) { + flags |= viewEngine.DepFlags.Optional; + } + if (dep.isValue) { + flags |= viewEngine.DepFlags.Value; + } + return flags === viewEngine.DepFlags.None ? expr : o.literalArr([o.literal(flags), expr]); +} + +function hasViewContainer(ast: TemplateAst): boolean { + if (ast instanceof EmbeddedTemplateAst) { + return ast.hasViewContainer; + } else if (ast instanceof ElementAst) { + return ast.hasViewContainer; + } + return false; +} + +function calcQueryId(queryId: QueryId): string { + if (queryId.directiveIndex == null) { + // view query + return `v${queryId.queryIndex}`; + } else { + return `c${queryId.elementDepth}_${queryId.directiveIndex}_${queryId.queryIndex}`; + } +} + +function lifecycleHookToNodeFlag(lifecycleHook: LifecycleHooks): number { + let nodeFlag = viewEngine.NodeFlags.None; + switch (lifecycleHook) { + case LifecycleHooks.AfterContentChecked: + nodeFlag = viewEngine.NodeFlags.AfterContentChecked; + break; + case LifecycleHooks.AfterContentInit: + nodeFlag = viewEngine.NodeFlags.AfterContentInit; + break; + case LifecycleHooks.AfterViewChecked: + nodeFlag = viewEngine.NodeFlags.AfterViewChecked; + break; + case LifecycleHooks.AfterViewInit: + nodeFlag = viewEngine.NodeFlags.AfterViewInit; + break; + case LifecycleHooks.DoCheck: + nodeFlag = viewEngine.NodeFlags.DoCheck; + break; + case LifecycleHooks.OnChanges: + nodeFlag = viewEngine.NodeFlags.OnChanges; + break; + case LifecycleHooks.OnDestroy: + nodeFlag = viewEngine.NodeFlags.OnDestroy; + break; + case LifecycleHooks.OnInit: + nodeFlag = viewEngine.NodeFlags.OnInit; + break; + } + return nodeFlag; +} + +function elementBindingDefs(inputAsts: BoundElementPropertyAst[]): o.Expression[] { + return inputAsts.map((inputAst) => { + switch (inputAst.type) { + case PropertyBindingType.Attribute: + return o.literalArr([ + o.literal(viewEngine.BindingType.ElementAttribute), o.literal(inputAst.name), + o.literal(inputAst.securityContext) + ]); + case PropertyBindingType.Property: + return o.literalArr([ + o.literal(viewEngine.BindingType.ElementProperty), o.literal(inputAst.name), + o.literal(inputAst.securityContext) + ]); + case PropertyBindingType.Class: + return o.literalArr( + [o.literal(viewEngine.BindingType.ElementClass), o.literal(inputAst.name)]); + case PropertyBindingType.Style: + return o.literalArr([ + o.literal(viewEngine.BindingType.ElementStyle), o.literal(inputAst.name), + o.literal(inputAst.unit) + ]); + } + }); +} + + +function fixedAttrsDef(elementAst: ElementAst): o.LiteralMapExpr { + const mapResult: {[key: string]: string} = {}; + elementAst.attrs.forEach(attrAst => { mapResult[attrAst.name] = attrAst.value; }); + elementAst.directives.forEach(dirAst => { + Object.keys(dirAst.directive.hostAttributes).forEach(name => { + const value = dirAst.directive.hostAttributes[name]; + const prevValue = mapResult[name]; + mapResult[name] = prevValue != null ? mergeAttributeValue(name, prevValue, value) : value; + }); + }); + const mapEntries: o.LiteralMapEntry[] = []; + // Note: We need to sort to get a defined output order + // for tests and for caching generated artifacts... + Object.keys(mapResult).sort().forEach((attrName) => { + mapEntries.push(new o.LiteralMapEntry(attrName, o.literal(mapResult[attrName]), true)); + }); + return new o.LiteralMapExpr(mapEntries); +} + +function mergeAttributeValue(attrName: string, attrValue1: string, attrValue2: string): string { + if (attrName == CLASS_ATTR || attrName == STYLE_ATTR) { + return `${attrValue1} ${attrValue2}`; + } else { + return attrValue2; + } +} diff --git a/modules/@angular/compiler/test/template_parser/template_parser_spec.ts b/modules/@angular/compiler/test/template_parser/template_parser_spec.ts index 76939b90b5..832f10b193 100644 --- a/modules/@angular/compiler/test/template_parser/template_parser_spec.ts +++ b/modules/@angular/compiler/test/template_parser/template_parser_spec.ts @@ -92,14 +92,14 @@ export function main() { new class extends NullVisitor{ visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any) { return ast; } }, - new EmbeddedTemplateAst([], [], [], [], [], [], false, [], 0, null)); + new EmbeddedTemplateAst([], [], [], [], [], [], false, [], [], 0, null)); }); it('should visit ElementAst', () => { expectVisitedNode( new class extends NullVisitor{visitElement(ast: ElementAst, context: any) { return ast; }}, - new ElementAst('foo', [], [], [], [], [], [], false, [], 0, null, null)); + new ElementAst('foo', [], [], [], [], [], [], false, [], [], 0, null, null)); }); it('should visit RefererenceAst', () => { @@ -171,8 +171,8 @@ export function main() { }; const nodes: TemplateAst[] = [ new NgContentAst(0, 0, null), - new EmbeddedTemplateAst([], [], [], [], [], [], false, [], 0, null), - new ElementAst('foo', [], [], [], [], [], [], false, [], 0, null, null), + new EmbeddedTemplateAst([], [], [], [], [], [], false, [], [], 0, null), + new ElementAst('foo', [], [], [], [], [], [], false, [], [], 0, null, null), new ReferenceAst('foo', null, null), new VariableAst('foo', 'bar', null), new BoundEventAst('foo', 'bar', 'goo', null, null), new BoundElementPropertyAst('foo', null, null, false, null, 'bar', null), @@ -2088,7 +2088,7 @@ class FooAstTransformer extends ThrowingVisitor { visitElement(ast: ElementAst, context: any): any { if (ast.name != 'div') return ast; return new ElementAst( - 'foo', [], [], [], [], [], [], false, [], ast.ngContentIndex, ast.sourceSpan, + 'foo', [], [], [], [], [], [], false, [], [], ast.ngContentIndex, ast.sourceSpan, ast.endSourceSpan); } } @@ -2097,7 +2097,7 @@ class BarAstTransformer extends FooAstTransformer { visitElement(ast: ElementAst, context: any): any { if (ast.name != 'foo') return ast; return new ElementAst( - 'bar', [], [], [], [], [], [], false, [], ast.ngContentIndex, ast.sourceSpan, + 'bar', [], [], [], [], [], [], false, [], [], ast.ngContentIndex, ast.sourceSpan, ast.endSourceSpan); } } diff --git a/modules/@angular/core/src/application_module.ts b/modules/@angular/core/src/application_module.ts index a52b568979..632e3397dc 100644 --- a/modules/@angular/core/src/application_module.ts +++ b/modules/@angular/core/src/application_module.ts @@ -7,7 +7,7 @@ */ import {AnimationQueue} from './animation/animation_queue'; -import {ApplicationInitStatus} from './application_init'; +import {APP_INITIALIZER, ApplicationInitStatus} from './application_init'; import {ApplicationRef, ApplicationRef_} from './application_ref'; import {APP_ID_RANDOM_PROVIDER} from './application_tokens'; import {IterableDiffers, KeyValueDiffers, defaultIterableDiffers, defaultKeyValueDiffers} from './change_detection/change_detection'; @@ -16,6 +16,7 @@ import {LOCALE_ID} from './i18n/tokens'; import {Compiler} from './linker/compiler'; import {ViewUtils} from './linker/view_utils'; import {NgModule} from './metadata'; +import {initServicesIfNeeded} from './view/index'; export function _iterableDiffersFactory() { return defaultIterableDiffers; @@ -29,6 +30,10 @@ export function _localeFactory(locale?: string): string { return locale || 'en-US'; } +export function _initViewEngine() { + initServicesIfNeeded(); +} + /** * This module includes the providers of @angular/core that are needed * to bootstrap components via `ApplicationRef`. @@ -51,6 +56,7 @@ export function _localeFactory(locale?: string): string { useFactory: _localeFactory, deps: [[new Inject(LOCALE_ID), new Optional(), new SkipSelf()]] }, + {provide: APP_INITIALIZER, useValue: _initViewEngine, multi: true}, ] }) export class ApplicationModule { diff --git a/modules/@angular/core/src/core_private_export.ts b/modules/@angular/core/src/core_private_export.ts index 68680f8b85..4c1da19a4a 100644 --- a/modules/@angular/core/src/core_private_export.ts +++ b/modules/@angular/core/src/core_private_export.ts @@ -42,6 +42,7 @@ import * as reflection_types from './reflection/types'; import * as api from './render/api'; import * as decorators from './util/decorators'; import {isObservable, isPromise} from './util/lang'; +import * as viewEngine from './view/index'; export const __core_private__: { isDefaultChangeDetectionStrategy: typeof constants.isDefaultChangeDetectionStrategy, @@ -109,6 +110,7 @@ export const __core_private__: { AnimationTransition: typeof AnimationTransition view_utils: typeof view_utils, ERROR_COMPONENT_TYPE: typeof ERROR_COMPONENT_TYPE, + viewEngine: typeof viewEngine, } = { isDefaultChangeDetectionStrategy: constants.isDefaultChangeDetectionStrategy, ChangeDetectorStatus: constants.ChangeDetectorStatus, @@ -125,6 +127,7 @@ export const __core_private__: { registerModuleFactory: ng_module_factory_loader.registerModuleFactory, ViewType: view_type.ViewType, view_utils: view_utils, + viewEngine: viewEngine, ViewMetadata: metadata_view.ViewMetadata, DebugContext: debug_context.DebugContext, StaticNodeDebugInfo: debug_context.StaticNodeDebugInfo, diff --git a/modules/@angular/core/src/view/element.ts b/modules/@angular/core/src/view/element.ts index 7caf65e994..7c518345c7 100644 --- a/modules/@angular/core/src/view/element.ts +++ b/modules/@angular/core/src/view/element.ts @@ -9,18 +9,19 @@ import {isDevMode} from '../application_ref'; import {SecurityContext} from '../security'; -import {BindingDef, BindingType, DebugContext, DisposableFn, ElementData, ElementOutputDef, NodeData, NodeDef, NodeFlags, NodeType, QueryValueType, Services, ViewData, ViewDefinition, ViewFlags, asElementData} from './types'; -import {checkAndUpdateBinding, dispatchEvent, sliceErrorStack, unwrapValue} from './util'; +import {BindingDef, BindingType, DebugContext, DisposableFn, ElementData, ElementOutputDef, NodeData, NodeDef, NodeFlags, NodeType, QueryValueType, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, asElementData} from './types'; +import {checkAndUpdateBinding, dispatchEvent, elementEventFullName, resolveViewDefinition, sliceErrorStack, unwrapValue} from './util'; export function anchorDef( flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number, - childCount: number, template?: ViewDefinition): NodeDef { + childCount: number, templateFactory?: ViewDefinitionFactory): NodeDef { const matchedQueryDefs: {[queryId: string]: QueryValueType} = {}; if (matchedQueries) { matchedQueries.forEach(([queryId, valueType]) => { matchedQueryDefs[queryId] = valueType; }); } // skip the call to sliceErrorStack itself + the call to this function. const source = isDevMode() ? sliceErrorStack(2, 3) : ''; + const template = templateFactory ? resolveViewDefinition(templateFactory) : null; return { type: NodeType.Element, // will bet set by the view definition @@ -130,10 +131,10 @@ export function elementDef( export function createElement(view: ViewData, renderHost: any, def: NodeDef): ElementData { const elDef = def.element; - const rootElement = view.root.element; + const rootSelectorOrNode = view.root.selectorOrNode; const renderer = view.root.renderer; let el: any; - if (view.parent || !rootElement) { + if (view.parent || !rootSelectorOrNode) { const parentNode = def.parent != null ? asElementData(view, def.parent).renderElement : renderHost; el = elDef.name ? renderer.createElement(elDef.name) : renderer.createComment(''); @@ -141,7 +142,7 @@ export function createElement(view: ViewData, renderHost: any, def: NodeDef): El renderer.appendChild(parentNode, el); } } else { - el = rootElement; + el = renderer.selectRootElement(rootSelectorOrNode); } if (elDef.attrs) { for (let attrName in elDef.attrs) { @@ -151,7 +152,8 @@ export function createElement(view: ViewData, renderHost: any, def: NodeDef): El if (elDef.outputs.length) { for (let i = 0; i < elDef.outputs.length; i++) { const output = elDef.outputs[i]; - const handleEventClosure = renderEventHandlerClosure(view, def.index, output.eventName); + const handleEventClosure = renderEventHandlerClosure( + view, def.index, elementEventFullName(output.target, output.eventName)); const disposable = renderer.listen(output.target || el, output.eventName, handleEventClosure); view.disposables[def.disposableIndex + i] = disposable; diff --git a/modules/@angular/core/src/view/index.ts b/modules/@angular/core/src/view/index.ts index fdf50b8089..e1412f9c2e 100644 --- a/modules/@angular/core/src/view/index.ts +++ b/modules/@angular/core/src/view/index.ts @@ -14,7 +14,7 @@ export {queryDef} from './query'; export {createComponentFactory} from './refs'; export {initServicesIfNeeded} from './services'; export {textDef} from './text'; -export {rootRenderNodes} from './util'; +export {elementEventFullName, nodeValue, rootRenderNodes} from './util'; export {viewDef} from './view'; export {attachEmbeddedView, detachEmbeddedView, moveEmbeddedView} from './view_attach'; diff --git a/modules/@angular/core/src/view/provider.ts b/modules/@angular/core/src/view/provider.ts index 191e98f245..ab5f68d1f7 100644 --- a/modules/@angular/core/src/view/provider.ts +++ b/modules/@angular/core/src/view/provider.ts @@ -16,7 +16,7 @@ import {Type} from '../type'; import {createChangeDetectorRef, createInjector, createTemplateRef, createViewContainerRef} from './refs'; import {BindingDef, BindingType, DepDef, DepFlags, DisposableFn, NodeData, NodeDef, NodeFlags, NodeType, ProviderData, ProviderOutputDef, ProviderType, QueryBindingType, QueryDef, QueryValueType, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewState, asElementData, asProviderData} from './types'; -import {checkAndUpdateBinding, dispatchEvent, findElementDef, isComponentView, parentDiIndex, tokenKey, unwrapValue} from './util'; +import {checkAndUpdateBinding, dispatchEvent, isComponentView, tokenKey, unwrapValue, viewParentDiIndex} from './util'; const RendererV1TokenKey = tokenKey(v1renderer.Renderer); const ElementRefTokenKey = tokenKey(ElementRef); @@ -278,6 +278,9 @@ function callFactory( export function resolveDep( view: ViewData, requestNodeIndex: number, elIndex: number, depDef: DepDef, notFoundValue = Injector.THROW_IF_NOT_FOUND): any { + if (depDef.flags & DepFlags.Value) { + return depDef.token; + } const startView = view; if (depDef.flags & DepFlags.Optional) { notFoundValue = null; @@ -288,7 +291,7 @@ export function resolveDep( requestNodeIndex = null; elIndex = view.def.nodes[elIndex].parent; while (elIndex == null && view) { - elIndex = parentDiIndex(view); + elIndex = viewParentDiIndex(view); view = view.parent; } } @@ -337,7 +340,7 @@ export function resolveDep( } } requestNodeIndex = null; - elIndex = parentDiIndex(view); + elIndex = viewParentDiIndex(view); view = view.parent; } return startView.root.injector.get(depDef.token, notFoundValue); diff --git a/modules/@angular/core/src/view/refs.ts b/modules/@angular/core/src/view/refs.ts index 8fe04dc0a7..159f19a1f8 100644 --- a/modules/@angular/core/src/view/refs.ts +++ b/modules/@angular/core/src/view/refs.ts @@ -20,7 +20,7 @@ import {Type} from '../type'; import {DirectDomRenderer, LegacyRendererAdapter} from './renderer'; import {ArgumentType, BindingType, DebugContext, DepFlags, ElementData, NodeCheckFn, NodeData, NodeDef, NodeType, RendererV2, RootData, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewState, asElementData, asProviderData} from './types'; -import {findElementDef, isComponentView, parentDiIndex, renderNode, resolveViewDefinition, rootRenderNodes, tokenKey} from './util'; +import {isComponentView, renderNode, resolveViewDefinition, rootRenderNodes, tokenKey, viewParentDiIndex} from './util'; const EMPTY_CONTEXT = new Object(); @@ -99,7 +99,7 @@ class ViewContainerRef_ implements ViewContainerRef { let view = this._view; let elIndex = view.def.nodes[this._elIndex].parent; while (elIndex == null && view) { - elIndex = parentDiIndex(view); + elIndex = viewParentDiIndex(view); view = view.parent; } return view ? new Injector_(view, elIndex) : this._view.root.injector; @@ -119,7 +119,7 @@ class ViewContainerRef_ implements ViewContainerRef { createEmbeddedView(templateRef: TemplateRef, context?: C, index?: number): EmbeddedViewRef { - const viewRef = templateRef.createEmbeddedView(context); + const viewRef = templateRef.createEmbeddedView(context || {}); this.insert(viewRef, index); return viewRef; } diff --git a/modules/@angular/core/src/view/renderer.ts b/modules/@angular/core/src/view/renderer.ts index 86cbf2fa0d..77992597ac 100644 --- a/modules/@angular/core/src/view/renderer.ts +++ b/modules/@angular/core/src/view/renderer.ts @@ -40,6 +40,8 @@ export class DirectDomRenderer implements RendererV2 { nextSibling(node: any): any { return node.nextSiblibng; } setAttribute(el: any, name: string, value: string): void { return el.setAttribute(name, value); } removeAttribute(el: any, name: string): void { el.removeAttribute(name); } + setBindingDebugInfo(el: any, propertyName: string, propertyValue: string): void {} + removeBindingDebugInfo(el: any, propertyName: string): void {} addClass(el: any, name: string): void { el.classList.add(name); } removeClass(el: any, name: string): void { el.classList.remove(name); } setStyle(el: any, style: string, value: any): void { el.style[style] = value; } @@ -97,8 +99,11 @@ export class LegacyRendererAdapter implements RendererV2 { } appendChild(parent: any, newChild: any): void { this._delegate.projectNodes(parent, [newChild]); } insertBefore(parent: any, newChild: any, refChild: any): void { - const beforeSibling = refChild.nextSiblingOf ? refChild.nextSiblingOf : refChild; - this._delegate.attachViewAfter(beforeSibling, [newChild]); + if (refChild) { + this._delegate.attachViewAfter(refChild.previousSibling, [newChild]); + } else { + this.appendChild(parent, newChild); + } } removeChild(parent: any, oldChild: any): void { if (parent) { @@ -108,14 +113,20 @@ export class LegacyRendererAdapter implements RendererV2 { selectRootElement(selectorOrNode: any, debugInfo?: DebugContext): any { return this._delegate.selectRootElement(selectorOrNode, debugInfo); } - parentNode(node: any): any { return {parentOf: node}; } - nextSibling(node: any): any { return {nextSiblingOf: node}; } + parentNode(node: any): any { return node.parentNode; } + nextSibling(node: any): any { return node.nextSibling; } setAttribute(el: any, name: string, value: string): void { this._delegate.setElementAttribute(el, name, value); } removeAttribute(el: any, name: string): void { this._delegate.setElementAttribute(el, name, null); } + setBindingDebugInfo(el: any, propertyName: string, propertyValue: string): void { + this._delegate.setBindingDebugInfo(el, propertyName, propertyValue); + } + removeBindingDebugInfo(el: any, propertyName: string): void { + this._delegate.setBindingDebugInfo(el, propertyName, null); + } addClass(el: any, name: string): void { this._delegate.setElementClass(el, name, true); } removeClass(el: any, name: string): void { this._delegate.setElementClass(el, name, false); } setStyle(el: any, style: string, value: any): void { diff --git a/modules/@angular/core/src/view/services.ts b/modules/@angular/core/src/view/services.ts index 1e9c36ed30..ca98c79dd1 100644 --- a/modules/@angular/core/src/view/services.ts +++ b/modules/@angular/core/src/view/services.ts @@ -19,8 +19,8 @@ import {resolveDep} from './provider'; import {getQueryValue} from './query'; import {createInjector} from './refs'; import {DirectDomRenderer, LegacyRendererAdapter} from './renderer'; -import {ArgumentType, BindingType, DebugContext, DepFlags, ElementData, NodeCheckFn, NodeData, NodeDef, NodeType, RendererV2, RootData, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewState, asElementData, asProviderData} from './types'; -import {checkBinding, findElementDef, isComponentView, parentDiIndex, renderNode, resolveViewDefinition, rootRenderNodes} from './util'; +import {ArgumentType, BindingType, DebugContext, DepFlags, ElementData, NodeCheckFn, NodeData, NodeDef, NodeFlags, NodeType, RendererV2, RootData, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewState, asElementData, asProviderData} from './types'; +import {checkBinding, isComponentView, queryIdIsReference, renderNode, resolveViewDefinition, rootRenderNodes, viewParentDiIndex} from './util'; import {checkAndUpdateView, checkNoChangesView, createEmbeddedView, createRootView, destroyView} from './view'; import {attachEmbeddedView, detachEmbeddedView, moveEmbeddedView} from './view_attach'; @@ -98,7 +98,7 @@ function debugCreateRootView( const debugRoot: RootData = { injector: root.injector, projectableNodes: root.projectableNodes, - element: root.element, + selectorOrNode: root.selectorOrNode, renderer: new DebugRenderer(root.renderer), sanitizer: root.sanitizer }; @@ -114,7 +114,7 @@ function createRootData( new DirectDomRenderer(); const rootElement = rootSelectorOrNode ? renderer.selectRootElement(rootSelectorOrNode) : undefined; - return {injector, projectableNodes, element: rootElement, sanitizer, renderer}; + return {injector, projectableNodes, selectorOrNode: rootSelectorOrNode, sanitizer, renderer}; } function debugCreateEmbeddedView(parent: ViewData, anchorDef: NodeDef, context?: any): ViewData { @@ -184,13 +184,16 @@ function debugUpdateView(check: NodeCheckFn, view: ViewData) { } function setBindingDebugInfo(renderer: RendererV2, renderNode: any, propName: string, value: any) { - try { - renderer.setAttribute( - renderNode, `ng-reflect-${camelCaseToDashCase(propName)}`, value ? value.toString() : null); - } catch (e) { - renderer.setAttribute( - renderNode, `ng-reflect-${camelCaseToDashCase(propName)}`, - '[ERROR] Exception while trying to serialize the value'); + const renderName = `ng-reflect-${camelCaseToDashCase(propName)}`; + if (value) { + try { + renderer.setBindingDebugInfo(renderNode, renderName, value.toString()); + } catch (e) { + renderer.setBindingDebugInfo( + renderNode, renderName, '[ERROR] Exception while trying to serialize the value'); + } + } else { + renderer.removeBindingDebugInfo(renderNode, renderName); } } @@ -240,6 +243,12 @@ class DebugRenderer implements RendererV2 { return this._delegate.setAttribute(el, name, value); } removeAttribute(el: any, name: string): void { return this._delegate.removeAttribute(el, name); } + setBindingDebugInfo(el: any, propertyName: string, propertyValue: string): void { + this._delegate.setBindingDebugInfo(el, propertyName, propertyValue); + } + removeBindingDebugInfo(el: any, propertyName: string): void { + this._delegate.removeBindingDebugInfo(el, propertyName); + } addClass(el: any, name: string): void { return this._delegate.addClass(el, name); } removeClass(el: any, name: string): void { return this._delegate.removeClass(el, name); } setStyle(el: any, style: string, value: any): void { @@ -258,27 +267,63 @@ class DebugRenderer implements RendererV2 { class DebugContext_ implements DebugContext { private nodeDef: NodeDef; + private elView: ViewData; private elDef: NodeDef; + private compProviderIndex: number; constructor(public view: ViewData, public nodeIndex: number) { if (nodeIndex == null) { - this.nodeIndex = nodeIndex = view.parentIndex; - this.view = view = view.parent; + this.nodeIndex = 0; } this.nodeDef = view.def.nodes[nodeIndex]; - this.elDef = findElementDef(view, nodeIndex); + let elIndex = nodeIndex; + let elView = view; + while (elIndex != null && view.def.nodes[elIndex].type !== NodeType.Element) { + elIndex = view.def.nodes[elIndex].parent; + } + if (elIndex == null) { + while (elIndex == null && elView) { + elIndex = viewParentDiIndex(elView); + elView = elView.parent; + } + } + this.elView = elView; + if (elView) { + this.elDef = elView.def.nodes[elIndex]; + + for (let i = this.elDef.index + 1; i <= this.elDef.index + this.elDef.childCount; i++) { + const childDef = this.elView.def.nodes[i]; + if (childDef.flags & NodeFlags.HasComponent) { + this.compProviderIndex = i; + break; + } + i += childDef.childCount; + } + } else { + this.elDef = null; + } + } + get injector(): Injector { return createInjector(this.elView, this.elDef.index); } + get component(): any { + if (this.compProviderIndex != null) { + return asProviderData(this.elView, this.compProviderIndex).instance; + } + return this.view.component; + } + get context(): any { + if (this.compProviderIndex != null) { + return asProviderData(this.elView, this.compProviderIndex).instance; + } + return this.view.context; } - get injector(): Injector { return createInjector(this.view, this.elDef.index); } - get component(): any { return this.view.component; } get providerTokens(): any[] { const tokens: any[] = []; if (this.elDef) { for (let i = this.elDef.index + 1; i <= this.elDef.index + this.elDef.childCount; i++) { - const childDef = this.view.def.nodes[i]; + const childDef = this.elView.def.nodes[i]; if (childDef.type === NodeType.Provider) { tokens.push(childDef.provider.token); - } else { - i += childDef.childCount; } + i += childDef.childCount; } } return tokens; @@ -286,20 +331,18 @@ class DebugContext_ implements DebugContext { get references(): {[key: string]: any} { const references: {[key: string]: any} = {}; if (this.elDef) { - collectReferences(this.view, this.elDef, references); + collectReferences(this.elView, this.elDef, references); for (let i = this.elDef.index + 1; i <= this.elDef.index + this.elDef.childCount; i++) { - const childDef = this.view.def.nodes[i]; + const childDef = this.elView.def.nodes[i]; if (childDef.type === NodeType.Provider) { - collectReferences(this.view, childDef, references); - } else { - i += childDef.childCount; + collectReferences(this.elView, childDef, references); } + i += childDef.childCount; } } return references; } - get context(): any { return this.view.context; } get source(): string { if (this.nodeDef.type === NodeType.Text) { return this.nodeDef.text.source; @@ -308,12 +351,15 @@ class DebugContext_ implements DebugContext { } } get componentRenderElement() { - const elData = findHostElement(this.view); + const view = this.compProviderIndex != null ? + asProviderData(this.elView, this.compProviderIndex).componentView : + this.view; + const elData = findHostElement(view); return elData ? elData.renderElement : undefined; } get renderNode(): any { - let nodeDef = this.nodeDef.type === NodeType.Text ? this.nodeDef : this.elDef; - return renderNode(this.view, nodeDef); + return this.nodeDef.type === NodeType.Text ? renderNode(this.view, this.nodeDef) : + renderNode(this.elView, this.elDef); } } @@ -330,7 +376,7 @@ function findHostElement(view: ViewData): ElementData { function collectReferences(view: ViewData, nodeDef: NodeDef, references: {[key: string]: any}) { for (let queryId in nodeDef.matchedQueries) { - if (queryId.startsWith('#')) { + if (queryIdIsReference(queryId)) { references[queryId.slice(1)] = getQueryValue(view, nodeDef, queryId); } } diff --git a/modules/@angular/core/src/view/types.ts b/modules/@angular/core/src/view/types.ts index 82fbe4bcaf..5755257024 100644 --- a/modules/@angular/core/src/view/types.ts +++ b/modules/@angular/core/src/view/types.ts @@ -72,8 +72,7 @@ export enum ArgumentType { */ export enum ViewFlags { None = 0, - DirectDom = 1 << 1, - OnPush = 1 << 2 + OnPush = 1 << 1 } export interface ComponentDefinition { @@ -225,7 +224,8 @@ export interface DepDef { export enum DepFlags { None = 0, SkipSelf = 1 << 0, - Optional = 1 << 1 + Optional = 1 << 1, + Value = 2 << 2 } export interface ProviderOutputDef { @@ -407,7 +407,7 @@ export function asQueryList(view: ViewData, index: number): QueryList { export interface RootData { injector: Injector; projectableNodes: any[][]; - element: any; + selectorOrNode: any; renderer: RendererV2; sanitizer: Sanitizer; } @@ -436,6 +436,14 @@ export interface RendererV2 { * the caller can't rely on checking whether this is null or not. */ nextSibling(node: any): any; + /** + * Used only in debug mode to serialize property changes to dom nodes as attributes. + */ + setBindingDebugInfo(el: any, propertyName: string, propertyValue: string): void; + /** + * Used only in debug mode to serialize property changes to dom nodes as attributes. + */ + removeBindingDebugInfo(el: any, propertyName: string): void; setAttribute(el: any, name: string, value: string): void; removeAttribute(el: any, name: string): void; addClass(el: any, name: string): void; diff --git a/modules/@angular/core/src/view/util.ts b/modules/@angular/core/src/view/util.ts index eff2bf90a2..0af4fdb2de 100644 --- a/modules/@angular/core/src/view/util.ts +++ b/modules/@angular/core/src/view/util.ts @@ -95,27 +95,14 @@ export function declaredViewContainer(view: ViewData): ElementData { * for embedded views, this is the index of the parent node * that contains the view container. */ -export function parentDiIndex(view: ViewData): number { - if (view.parent) { - const parentNodeDef = view.def.nodes[view.parentIndex]; - return parentNodeDef.element && parentNodeDef.element.template ? parentNodeDef.parent : - parentNodeDef.index; +export function viewParentDiIndex(view: ViewData): number { + if (view.parent && view.context !== view.component) { + const parentNodeDef = view.parent.def.nodes[view.parentIndex]; + return parentNodeDef.parent; } return view.parentIndex; } -export function findElementDef(view: ViewData, nodeIndex: number): NodeDef { - const viewDef = view.def; - let nodeDef = viewDef.nodes[nodeIndex]; - while (nodeDef) { - if (nodeDef.type === NodeType.Element) { - return nodeDef; - } - nodeDef = nodeDef.parent != null ? viewDef.nodes[nodeDef.parent] : undefined; - } - return undefined; -} - export function renderNode(view: ViewData, def: NodeDef): any { switch (def.type) { case NodeType.Element: @@ -125,6 +112,27 @@ export function renderNode(view: ViewData, def: NodeDef): any { } } +export function nodeValue(view: ViewData, index: number): any { + const def = view.def.nodes[index]; + switch (def.type) { + case NodeType.Element: + return asElementData(view, def.index).renderElement; + case NodeType.Text: + return asTextData(view, def.index).renderText; + case NodeType.Provider: + return asProviderData(view, def.index).instance; + } + return undefined; +} + +export function queryIdIsReference(queryId: string): boolean { + return queryId.startsWith('#'); +} + +export function elementEventFullName(target: string, name: string): string { + return target ? `${target}:${name}` : name; +} + export function isComponentView(view: ViewData): boolean { return view.component === view.context && !!view.parent; } diff --git a/modules/@angular/core/src/view/view.ts b/modules/@angular/core/src/view/view.ts index 35ebc2562b..38133e8af2 100644 --- a/modules/@angular/core/src/view/view.ts +++ b/modules/@angular/core/src/view/view.ts @@ -7,6 +7,7 @@ */ import {ViewEncapsulation} from '../metadata/view'; + import {checkAndUpdateElementDynamic, checkAndUpdateElementInline, createElement} from './element'; import {expressionChangedAfterItHasBeenCheckedError} from './errors'; import {appendNgContent} from './ng_content'; @@ -15,7 +16,7 @@ import {checkAndUpdatePureExpressionDynamic, checkAndUpdatePureExpressionInline, import {checkAndUpdateQuery, createQuery, queryDef} from './query'; import {checkAndUpdateTextDynamic, checkAndUpdateTextInline, createText} from './text'; import {ArgumentType, ComponentDefinition, ElementDef, NodeData, NodeDef, NodeFlags, NodeType, ProviderData, ProviderDef, RootData, Services, TextDef, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewHandleEventFn, ViewState, ViewUpdateFn, asElementData, asProviderData, asPureExpressionData, asQueryList} from './types'; -import {checkBindingNoChanges, isComponentView, resolveViewDefinition} from './util'; +import {checkBindingNoChanges, isComponentView, queryIdIsReference, resolveViewDefinition} from './util'; const NOOP = (): any => undefined; @@ -41,7 +42,7 @@ export function viewDef( const newParent = nodes[currentParent.parent]; if (newParent) { newParent.childFlags |= currentParent.childFlags; - copyInto(currentParent.childMatchedQueries, newParent.childMatchedQueries); + copyQueryMatchesInto(currentParent.childMatchedQueries, newParent.childMatchedQueries); } currentParent = newParent; } @@ -64,17 +65,18 @@ export function viewDef( } nodes[i] = node; reverseChildNodes[reverseChildIndex] = node; - validateNode(currentParent, node); + validateNode(currentParent, node, nodesWithoutIndices.length); viewNodeFlags |= node.flags; - copyInto(node.matchedQueries, viewMatchedQueries); + copyQueryMatchesInto(node.matchedQueries, viewMatchedQueries); viewBindingCount += node.bindings.length; viewDisposableCount += node.disposableCount; if (currentParent) { currentParent.childFlags |= node.flags; - copyInto(node.matchedQueries, currentParent.childMatchedQueries); + copyQueryMatchesInto(node.matchedQueries, currentParent.childMatchedQueries); if (node.element && node.element.template) { - copyInto(node.element.template.nodeMatchedQueries, currentParent.childMatchedQueries); + copyQueryMatchesInto( + node.element.template.nodeMatchedQueries, currentParent.childMatchedQueries); } } @@ -96,7 +98,7 @@ export function viewDef( const newParent = nodes[currentParent.parent]; if (newParent) { newParent.childFlags |= currentParent.childFlags; - copyInto(currentParent.childMatchedQueries, newParent.childMatchedQueries); + copyQueryMatchesInto(currentParent.childMatchedQueries, newParent.childMatchedQueries); } currentParent = newParent; } @@ -114,9 +116,12 @@ export function viewDef( }; } -function copyInto(source: any, target: any) { +function copyQueryMatchesInto( + source: {[queryId: string]: any}, target: {[queryId: string]: boolean}) { for (let prop in source) { - target[prop] = source[prop]; + if (!queryIdIsReference(prop)) { + target[prop] = true; + } } } @@ -159,7 +164,7 @@ function calculateReverseChildIndex( return parentEndIndexInReverseChildOrder - lastChildOffsetRelativeToParentInDfsOrder; } -function validateNode(parent: NodeDef, node: NodeDef) { +function validateNode(parent: NodeDef, node: NodeDef, nodeCount: number) { const template = node.element && node.element.template; if (template) { if (template.lastRootNode && template.lastRootNode.flags & NodeFlags.HasEmbeddedViews) { @@ -182,12 +187,10 @@ function validateNode(parent: NodeDef, node: NodeDef) { } } if (node.childCount) { - if (parent) { - const parentEnd = parent.index + parent.childCount; - if (node.index <= parentEnd && node.index + node.childCount > parentEnd) { - throw new Error( - `Illegal State: childCount of node leads outside of parent, at index ${node.index}!`); - } + const parentEnd = parent ? parent.index + parent.childCount : nodeCount - 1; + if (node.index <= parentEnd && node.index + node.childCount > parentEnd) { + throw new Error( + `Illegal State: childCount of node leads outside of parent, at index ${node.index}!`); } } } diff --git a/modules/@angular/core/test/linker/integration_spec.ts b/modules/@angular/core/test/linker/integration_spec.ts index 2c70e454c2..00730dd2d0 100644 --- a/modules/@angular/core/test/linker/integration_spec.ts +++ b/modules/@angular/core/test/linker/integration_spec.ts @@ -7,6 +7,7 @@ */ import {CommonModule} from '@angular/common'; +import {USE_VIEW_ENGINE} from '@angular/compiler/src/config'; import {ComponentFactory, Host, Inject, Injectable, InjectionToken, Injector, NO_ERRORS_SCHEMA, NgModule, OnDestroy, ReflectiveInjector, SkipSelf} from '@angular/core'; import {ChangeDetectionStrategy, ChangeDetectorRef, PipeTransform} from '@angular/core/src/change_detection/change_detection'; import {getDebugContext} from '@angular/core/src/errors'; @@ -29,12 +30,21 @@ import {isBlank, isPresent, stringify} from '../../src/facade/lang'; const ANCHOR_ELEMENT = new InjectionToken('AnchorElement'); export function main() { - describe('jit', () => { declareTests({useJit: true}); }); + describe('jit', () => { declareTests({useJit: true, viewEngine: false}); }); - describe('no jit', () => { declareTests({useJit: false}); }); + describe('no jit', () => { declareTests({useJit: false, viewEngine: false}); }); + + describe('view engine', () => { + beforeEach(() => { + TestBed.configureCompiler( + {useJit: true, providers: [{provide: USE_VIEW_ENGINE, useValue: true}]}); + }); + + declareTests({useJit: true, viewEngine: true}); + }); } -function declareTests({useJit}: {useJit: boolean}) { +function declareTests({useJit, viewEngine}: {useJit: boolean, viewEngine: boolean}) { describe('integration tests', function() { beforeEach(() => { TestBed.configureCompiler({useJit: useJit}); }); @@ -255,7 +265,7 @@ function declareTests({useJit}: {useJit: boolean}) { }); describe('pipes', () => { - it('should support pipes in bindings', () => { + viewEngine || it('should support pipes in bindings', () => { TestBed.configureTestingModule({declarations: [MyComp, MyDir, DoublePipe]}); const template = '
'; TestBed.overrideComponent(MyComp, {set: {template}}); @@ -520,7 +530,7 @@ function declareTests({useJit}: {useJit: boolean}) { const fixture = TestBed.createComponent(MyComp); const value = fixture.debugElement.childNodes[0].references['alice']; - expect(value).toBeAnInstanceOf(TemplateRef_); + expect(value.createEmbeddedView).toBeTruthy(); }); it('should preserve case', () => { @@ -535,7 +545,7 @@ function declareTests({useJit}: {useJit: boolean}) { }); describe('variables', () => { - it('should allow to use variables in a for loop', () => { + viewEngine || it('should allow to use variables in a for loop', () => { TestBed.configureTestingModule({declarations: [MyComp, ChildCompNoTemplate]}); const template = ''; @@ -660,29 +670,31 @@ function declareTests({useJit}: {useJit: boolean}) { }); if (getDOM().supportsDOMEvents()) { - it('should be checked when an async pipe requests a check', fakeAsync(() => { - TestBed.configureTestingModule( - {declarations: [MyComp, PushCmpWithAsyncPipe], imports: [CommonModule]}); - const template = ''; - TestBed.overrideComponent(MyComp, {set: {template}}); - const fixture = TestBed.createComponent(MyComp); + viewEngine || + it('should be checked when an async pipe requests a check', fakeAsync(() => { + TestBed.configureTestingModule( + {declarations: [MyComp, PushCmpWithAsyncPipe], imports: [CommonModule]}); + const template = ''; + TestBed.overrideComponent(MyComp, {set: {template}}); + const fixture = TestBed.createComponent(MyComp); - tick(); + tick(); - const cmp: PushCmpWithAsyncPipe = fixture.debugElement.children[0].references['cmp']; - fixture.detectChanges(); - expect(cmp.numberOfChecks).toEqual(1); + const cmp: PushCmpWithAsyncPipe = + fixture.debugElement.children[0].references['cmp']; + fixture.detectChanges(); + expect(cmp.numberOfChecks).toEqual(1); - fixture.detectChanges(); - fixture.detectChanges(); - expect(cmp.numberOfChecks).toEqual(1); + fixture.detectChanges(); + fixture.detectChanges(); + expect(cmp.numberOfChecks).toEqual(1); - cmp.resolve(2); - tick(); + cmp.resolve(2); + tick(); - fixture.detectChanges(); - expect(cmp.numberOfChecks).toEqual(2); - })); + fixture.detectChanges(); + expect(cmp.numberOfChecks).toEqual(2); + })); } }); @@ -885,24 +897,26 @@ function declareTests({useJit}: {useJit: boolean}) { expect(tc.nativeElement.id).toEqual('newId'); }); - it('should not use template variables for expressions in hostProperties', () => { - @Directive({selector: '[host-properties]', host: {'[id]': 'id', '[title]': 'unknownProp'}}) - class DirectiveWithHostProps { - id = 'one'; - } + viewEngine || + it('should not use template variables for expressions in hostProperties', () => { + @Directive( + {selector: '[host-properties]', host: {'[id]': 'id', '[title]': 'unknownProp'}}) + class DirectiveWithHostProps { + id = 'one'; + } - const fixture = - TestBed.configureTestingModule({declarations: [MyComp, DirectiveWithHostProps]}) - .overrideComponent( - MyComp, - {set: {template: `
`}}) - .createComponent(MyComp); - fixture.detectChanges(); + const fixture = + TestBed.configureTestingModule({declarations: [MyComp, DirectiveWithHostProps]}) + .overrideComponent(MyComp, { + set: {template: `
`} + }) + .createComponent(MyComp); + fixture.detectChanges(); - const tc = fixture.debugElement.children[0]; - expect(tc.properties['id']).toBe('one'); - expect(tc.properties['title']).toBe(undefined); - }); + const tc = fixture.debugElement.children[0]; + expect(tc.properties['id']).toBe('one'); + expect(tc.properties['title']).toBe(undefined); + }); it('should not allow pipes in hostProperties', () => { @Directive({selector: '[host-properties]', host: {'[id]': 'id | uppercase'}}) @@ -916,7 +930,8 @@ function declareTests({useJit}: {useJit: boolean}) { .toThrowError(/Host binding expression cannot contain pipes/); }); - it('should not use template variables for expressions in hostListeners', () => { + // TODO: literal array + viewEngine || it('should not use template variables for expressions in hostListeners', () => { @Directive({selector: '[host-listener]', host: {'(click)': 'doIt(id, unknownProp)'}}) class DirectiveWithHostListener { id = 'one'; @@ -1232,7 +1247,7 @@ function declareTests({useJit}: {useJit: boolean}) { .toThrowError(`Directive ${stringify(SomeDirective)} has no selector, please add it!`); }); - it('should use a default element name for components without selectors', () => { + viewEngine || it('should use a default element name for components without selectors', () => { let noSelectorComponentFactory: ComponentFactory; @Component({template: '----'}) @@ -1264,7 +1279,7 @@ function declareTests({useJit}: {useJit: boolean}) { }); }); - describe('error handling', () => { + viewEngine || describe('error handling', () => { it('should report a meaningful error when a directive is missing annotation', () => { TestBed.configureTestingModule({declarations: [MyComp, SomeDirectiveMissingAnnotation]}); @@ -1721,7 +1736,7 @@ class MyService { class SimpleImperativeViewComponent { done: any; - constructor(self: ElementRef, renderer: Renderer) { + constructor(self: ElementRef) { const hostElement = self.nativeElement; getDOM().appendChild(hostElement, el('hello imp view')); } diff --git a/modules/@angular/core/test/view/component_view_spec.ts b/modules/@angular/core/test/view/component_view_spec.ts index 7f9eeb9d77..f91db5993c 100644 --- a/modules/@angular/core/test/view/component_view_spec.ts +++ b/modules/@angular/core/test/view/component_view_spec.ts @@ -63,7 +63,7 @@ export function main() { it('should select root elements based on a selector', () => { const view = createRootView( compViewDef([ - elementDef(NodeFlags.None, null, null, 1, 'div'), + elementDef(NodeFlags.None, null, null, 0, 'div'), ]), {}, [], 'root'); const rootNodes = rootRenderNodes(view); @@ -73,7 +73,7 @@ export function main() { it('should select root elements based on a node', () => { const view = createRootView( compViewDef([ - elementDef(NodeFlags.None, null, null, 1, 'div'), + elementDef(NodeFlags.None, null, null, 0, 'div'), ]), {}, [], rootNode); const rootNodes = rootRenderNodes(view); @@ -83,7 +83,7 @@ export function main() { it('should set attributes on the root node', () => { const view = createRootView( compViewDef([ - elementDef(NodeFlags.None, null, null, 1, 'div', {'a': 'b'}), + elementDef(NodeFlags.None, null, null, 0, 'div', {'a': 'b'}), ]), {}, [], rootNode); expect(rootNode.getAttribute('a')).toBe('b'); @@ -93,7 +93,7 @@ export function main() { rootNode.appendChild(document.createElement('div')); const view = createRootView( compViewDef([ - elementDef(NodeFlags.None, null, null, 1, 'div', {'a': 'b'}), + elementDef(NodeFlags.None, null, null, 0, 'div', {'a': 'b'}), ]), {}, [], rootNode); expect(rootNode.childNodes.length).toBe(0); diff --git a/modules/@angular/core/test/view/element_spec.ts b/modules/@angular/core/test/view/element_spec.ts index 13a37f91bc..5ef0b3b3de 100644 --- a/modules/@angular/core/test/view/element_spec.ts +++ b/modules/@angular/core/test/view/element_spec.ts @@ -262,7 +262,7 @@ export function main() { const handleEventArgs = handleEventSpy.calls.mostRecent().args; expect(handleEventArgs[0]).toBe(view); expect(handleEventArgs[1]).toBe(0); - expect(handleEventArgs[2]).toBe('windowClick'); + expect(handleEventArgs[2]).toBe('window:windowClick'); expect(handleEventArgs[3]).toBeTruthy(); Services.destroyView(view); @@ -288,7 +288,7 @@ export function main() { const handleEventArgs = handleEventSpy.calls.mostRecent().args; expect(handleEventArgs[0]).toBe(view); expect(handleEventArgs[1]).toBe(0); - expect(handleEventArgs[2]).toBe('documentClick'); + expect(handleEventArgs[2]).toBe('document:documentClick'); expect(handleEventArgs[3]).toBeTruthy(); Services.destroyView(view); diff --git a/modules/@angular/core/test/view/embedded_view_spec.ts b/modules/@angular/core/test/view/embedded_view_spec.ts index e325bc93eb..f332ac56b3 100644 --- a/modules/@angular/core/test/view/embedded_view_spec.ts +++ b/modules/@angular/core/test/view/embedded_view_spec.ts @@ -7,7 +7,7 @@ */ import {Injector, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation} from '@angular/core'; -import {ArgumentType, BindingType, NodeCheckFn, NodeDef, NodeFlags, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, attachEmbeddedView, detachEmbeddedView, directiveDef, elementDef, moveEmbeddedView, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index'; +import {ArgumentType, BindingType, NodeCheckFn, NodeDef, NodeFlags, RootData, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, attachEmbeddedView, detachEmbeddedView, directiveDef, elementDef, moveEmbeddedView, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index'; import {inject} from '@angular/core/testing'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; @@ -21,8 +21,8 @@ export function main() { return viewDef(viewFlags, nodes, update, handleEvent); } - function embeddedViewDef(nodes: NodeDef[], update?: ViewUpdateFn): ViewDefinition { - return viewDef(ViewFlags.None, nodes, update); + function embeddedViewDef(nodes: NodeDef[], update?: ViewUpdateFn): ViewDefinitionFactory { + return () => viewDef(ViewFlags.None, nodes, update); } function createAndGetRootNodes( @@ -38,7 +38,7 @@ export function main() { const {view: parentView, rootNodes} = createAndGetRootNodes( compViewDef([ - elementDef(NodeFlags.None, null, null, 2, 'div'), + elementDef(NodeFlags.None, null, null, 1, 'div'), anchorDef( NodeFlags.HasEmbeddedViews, null, null, 0, embeddedViewDef([elementDef(NodeFlags.None, null, null, 0, 'span')])), diff --git a/modules/@angular/core/test/view/ng_content_spec.ts b/modules/@angular/core/test/view/ng_content_spec.ts index a83ec98d62..8fcd3c0d5a 100644 --- a/modules/@angular/core/test/view/ng_content_spec.ts +++ b/modules/@angular/core/test/view/ng_content_spec.ts @@ -7,7 +7,7 @@ */ import {Injector, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, TemplateRef, ViewContainerRef, ViewEncapsulation, getDebugNode} from '@angular/core'; -import {DebugContext, NodeDef, NodeFlags, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, asProviderData, asTextData, attachEmbeddedView, detachEmbeddedView, directiveDef, elementDef, ngContentDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index'; +import {DebugContext, NodeDef, NodeFlags, RootData, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, asProviderData, asTextData, attachEmbeddedView, detachEmbeddedView, directiveDef, elementDef, ngContentDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index'; import {inject} from '@angular/core/testing'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; @@ -21,8 +21,8 @@ export function main() { return viewDef(viewFlags, nodes, update, handleEvent); } - function embeddedViewDef(nodes: NodeDef[], update?: ViewUpdateFn): ViewDefinition { - return viewDef(ViewFlags.None, nodes, update); + function embeddedViewDef(nodes: NodeDef[], update?: ViewUpdateFn): ViewDefinitionFactory { + return () => viewDef(ViewFlags.None, nodes, update); } function hostElDef(contentNodes: NodeDef[], viewNodes: NodeDef[]): NodeDef[] { diff --git a/modules/@angular/core/test/view/provider_spec.ts b/modules/@angular/core/test/view/provider_spec.ts index 8ff988c7c8..ef0963e839 100644 --- a/modules/@angular/core/test/view/provider_spec.ts +++ b/modules/@angular/core/test/view/provider_spec.ts @@ -8,7 +8,7 @@ import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, ChangeDetectorRef, DoCheck, ElementRef, EventEmitter, Injector, OnChanges, OnDestroy, OnInit, RenderComponentType, Renderer, RootRenderer, Sanitizer, SecurityContext, SimpleChange, TemplateRef, ViewContainerRef, ViewEncapsulation, WrappedValue, getDebugNode} from '@angular/core'; import {getDebugContext} from '@angular/core/src/errors'; -import {ArgumentType, BindingType, DebugContext, DepFlags, NodeDef, NodeFlags, ProviderType, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, asProviderData, directiveDef, elementDef, providerDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index'; +import {ArgumentType, BindingType, DebugContext, DepFlags, NodeDef, NodeFlags, ProviderType, RootData, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, asProviderData, directiveDef, elementDef, providerDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index'; import {TestBed, inject, withModule} from '@angular/core/testing'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; @@ -23,8 +23,8 @@ export function main() { viewFlags, nodes, update, handleEvent, 'someCompId', ViewEncapsulation.None, []); } - function embeddedViewDef(nodes: NodeDef[], update?: ViewUpdateFn): ViewDefinition { - return viewDef(ViewFlags.None, nodes, update); + function embeddedViewDef(nodes: NodeDef[], update?: ViewUpdateFn): ViewDefinitionFactory { + return () => viewDef(ViewFlags.None, nodes, update); } function createAndGetRootNodes(viewDef: ViewDefinition): {rootNodes: any[], view: ViewData} { diff --git a/modules/@angular/core/test/view/query_spec.ts b/modules/@angular/core/test/view/query_spec.ts index 9d098a5625..2d15622aae 100644 --- a/modules/@angular/core/test/view/query_spec.ts +++ b/modules/@angular/core/test/view/query_spec.ts @@ -8,7 +8,7 @@ import {ElementRef, Injector, QueryList, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, TemplateRef, ViewContainerRef, ViewEncapsulation, getDebugNode} from '@angular/core'; import {getDebugContext} from '@angular/core/src/errors'; -import {BindingType, DebugContext, NodeDef, NodeFlags, QueryBindingType, QueryValueType, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, asProviderData, attachEmbeddedView, detachEmbeddedView, directiveDef, elementDef, queryDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index'; +import {BindingType, DebugContext, NodeDef, NodeFlags, QueryBindingType, QueryValueType, RootData, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, asProviderData, attachEmbeddedView, detachEmbeddedView, directiveDef, elementDef, queryDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index'; import {inject} from '@angular/core/testing'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; @@ -22,8 +22,8 @@ export function main() { return viewDef(viewFlags, nodes, update, handleEvent); } - function embeddedViewDef(nodes: NodeDef[], update?: ViewUpdateFn): ViewDefinition { - return viewDef(ViewFlags.None, nodes, update); + function embeddedViewDef(nodes: NodeDef[], update?: ViewUpdateFn): ViewDefinitionFactory { + return () => viewDef(ViewFlags.None, nodes, update); } function createAndGetRootNodes( @@ -118,7 +118,7 @@ export function main() { const {view} = createAndGetRootNodes(compViewDef([ elementDef(NodeFlags.None, null, null, 3, 'div'), ...viewQueryProviders(compViewDef([ - elementDef(NodeFlags.None, null, null, 1, 'span'), + elementDef(NodeFlags.None, null, null, 0, 'span'), ])), aServiceProvider(), ])); @@ -134,14 +134,10 @@ export function main() { const {view} = createAndGetRootNodes(compViewDef([ elementDef(NodeFlags.None, null, null, 5, 'div'), ...contentQueryProviders(), - anchorDef( - NodeFlags.HasEmbeddedViews, null, null, 2, - viewDef( - ViewFlags.None, - [ - elementDef(NodeFlags.None, null, null, 1, 'div'), - aServiceProvider(), - ])), + anchorDef(NodeFlags.HasEmbeddedViews, null, null, 2, embeddedViewDef([ + elementDef(NodeFlags.None, null, null, 1, 'div'), + aServiceProvider(), + ])), ...contentQueryProviders(), ])); @@ -164,14 +160,10 @@ export function main() { const {view} = createAndGetRootNodes(compViewDef([ elementDef(NodeFlags.None, null, null, 3, 'div'), ...contentQueryProviders(), - anchorDef( - NodeFlags.HasEmbeddedViews, null, null, 0, - viewDef( - ViewFlags.None, - [ - elementDef(NodeFlags.None, null, null, 1, 'div'), - aServiceProvider(), - ])), + anchorDef(NodeFlags.HasEmbeddedViews, null, null, 0, embeddedViewDef([ + elementDef(NodeFlags.None, null, null, 1, 'div'), + aServiceProvider(), + ])), elementDef(NodeFlags.None, null, null, 3, 'div'), ...contentQueryProviders(), anchorDef(NodeFlags.HasEmbeddedViews, null, null, 0), @@ -197,14 +189,10 @@ export function main() { const {view} = createAndGetRootNodes(compViewDef([ elementDef(NodeFlags.None, null, null, 3, 'div'), ...contentQueryProviders(), - anchorDef( - NodeFlags.HasEmbeddedViews, null, null, 0, - viewDef( - ViewFlags.None, - [ - elementDef(NodeFlags.None, null, null, 1, 'div'), - aServiceProvider(), - ])), + anchorDef(NodeFlags.HasEmbeddedViews, null, null, 0, embeddedViewDef([ + elementDef(NodeFlags.None, null, null, 1, 'div'), + aServiceProvider(), + ])), ])); Services.checkAndUpdateView(view); @@ -228,14 +216,10 @@ export function main() { const {view} = createAndGetRootNodes(compViewDef([ elementDef(NodeFlags.None, null, null, 2, 'div'), ...viewQueryProviders(compViewDef([ - anchorDef( - NodeFlags.HasEmbeddedViews, null, null, 0, - viewDef( - ViewFlags.None, - [ - elementDef(NodeFlags.None, null, null, 1, 'div'), - aServiceProvider(), - ])), + anchorDef(NodeFlags.HasEmbeddedViews, null, null, 0, embeddedViewDef([ + elementDef(NodeFlags.None, null, null, 1, 'div'), + aServiceProvider(), + ])), ])), ])); @@ -328,7 +312,7 @@ export function main() { const {view} = createAndGetRootNodes(compViewDef([ anchorDef( NodeFlags.None, [['query1', QueryValueType.TemplateRef]], null, 2, - viewDef(ViewFlags.None, [anchorDef(NodeFlags.None, null, null, 0)])), + embeddedViewDef([anchorDef(NodeFlags.None, null, null, 0)])), directiveDef(NodeFlags.None, null, 1, QueryService, []), queryDef(NodeFlags.HasContentQuery, 'query1', {'a': QueryBindingType.First}), ])); @@ -360,16 +344,12 @@ export function main() { describe('general binding behavior', () => { it('should checkNoChanges', () => { const {view} = createAndGetRootNodes(compViewDef([ - elementDef(NodeFlags.None, null, null, 4, 'div'), + elementDef(NodeFlags.None, null, null, 3, 'div'), ...contentQueryProviders(), - anchorDef( - NodeFlags.HasEmbeddedViews, null, null, 1, - viewDef( - ViewFlags.None, - [ - elementDef(NodeFlags.None, null, null, 1, 'div'), - aServiceProvider(), - ])), + anchorDef(NodeFlags.HasEmbeddedViews, null, null, 0, embeddedViewDef([ + elementDef(NodeFlags.None, null, null, 1, 'div'), + aServiceProvider(), + ])), ])); Services.checkAndUpdateView(view); diff --git a/modules/@angular/core/test/view/view_def_spec.ts b/modules/@angular/core/test/view/view_def_spec.ts index fa9b24da5d..4183843ec8 100644 --- a/modules/@angular/core/test/view/view_def_spec.ts +++ b/modules/@angular/core/test/view/view_def_spec.ts @@ -228,10 +228,10 @@ export function main() { elementDef(NodeFlags.None, null, null, 1, 'span'), anchorDef( NodeFlags.None, null, null, 0, - viewDef( + () => viewDef( ViewFlags.None, [ - elementDef(NodeFlags.None, [['q1', QueryValueType.Provider]], null, 1, 'span'), + elementDef(NodeFlags.None, [['q1', QueryValueType.Provider]], null, 0, 'span'), ])) ]); diff --git a/modules/benchmarks/src/tree/ng2_next/tree.ts b/modules/benchmarks/src/tree/ng2_next/tree.ts index aadd58da1b..9bbee91c9e 100644 --- a/modules/benchmarks/src/tree/ng2_next/tree.ts +++ b/modules/benchmarks/src/tree/ng2_next/tree.ts @@ -30,31 +30,35 @@ function TreeComponent_Host(): ViewDefinition { ]); } +function TreeComponent_1() { + return viewDef( + viewFlags, + [ + elementDef(NodeFlags.None, null, null, 1, 'tree'), + directiveDef( + NodeFlags.None, null, 0, TreeComponent, [], {data: [0, 'data']}, null, TreeComponent_0), + ], + (check, view) => { + const cmp = view.component; + check(view, 1, ArgumentType.Inline, cmp.data.left); + }); +} + +function TreeComponent_2() { + return viewDef( + viewFlags, + [ + elementDef(NodeFlags.None, null, null, 1, 'tree'), + directiveDef( + NodeFlags.None, null, 0, TreeComponent, [], {data: [0, 'data']}, null, TreeComponent_0), + ], + (check, view) => { + const cmp = view.component; + check(view, 1, ArgumentType.Inline, cmp.data.left); + }); +} + function TreeComponent_0(): ViewDefinition { - const TreeComponent_1: ViewDefinition = viewDef( - viewFlags, - [ - elementDef(NodeFlags.None, null, null, 1, 'tree'), - directiveDef( - NodeFlags.None, null, 0, TreeComponent, [], {data: [0, 'data']}, null, TreeComponent_0), - ], - (check, view) => { - const cmp = view.component; - check(view, 1, ArgumentType.Inline, cmp.data.left); - }); - - const TreeComponent_2: ViewDefinition = viewDef( - viewFlags, - [ - elementDef(NodeFlags.None, null, null, 1, 'tree'), - directiveDef( - NodeFlags.None, null, 0, TreeComponent, [], {data: [0, 'data']}, null, TreeComponent_0), - ], - (check, view) => { - const cmp = view.component; - check(view, 1, ArgumentType.Inline, cmp.data.left); - }); - return viewDef( viewFlags, [