From b9f17a9cb2eab57ebebc35d1547b9c26e74202e1 Mon Sep 17 00:00:00 2001 From: Tobias Bosch Date: Fri, 17 Feb 2017 08:56:36 -0800 Subject: [PATCH] fix: make all tests green with new view engine and JIT Note that this does not yet include enabling the view engine by default. Included refactoring: - view engine: split namespace of elements / attributes already when creating the `NodeDef` - view engine: when injecting the old `Renderer`, use an implementation that is based on `RendererV2` - view engine: store view queries in the component view, not on the host element --- .../common/test/directives/ng_class_spec.ts | 7 +- .../compiler/src/aot/compiler_factory.ts | 4 +- modules/@angular/compiler/src/config.ts | 5 +- .../src/directive_wrapper_compiler.ts | 3 +- .../@angular/compiler/src/i18n/extractor.ts | 2 +- modules/@angular/compiler/src/jit/compiler.ts | 7 +- .../compiler/src/jit/compiler_factory.ts | 13 +- .../compiler/src/metadata_resolver.ts | 12 +- .../src/template_parser/binding_parser.ts | 7 +- .../src/template_parser/template_parser.ts | 22 +- .../src/view_compiler_next/view_compiler.ts | 126 +++++----- .../core/src/linker/component_factory.ts | 8 +- modules/@angular/core/src/view/element.ts | 57 +++-- modules/@angular/core/src/view/index.ts | 4 +- modules/@angular/core/src/view/provider.ts | 14 +- .../@angular/core/src/view/pure_expression.ts | 1 + modules/@angular/core/src/view/query.ts | 20 +- modules/@angular/core/src/view/refs.ts | 216 ++++++++++++++++-- modules/@angular/core/src/view/services.ts | 11 +- modules/@angular/core/src/view/text.ts | 1 + modules/@angular/core/src/view/types.ts | 5 +- modules/@angular/core/src/view/util.ts | 44 ++-- modules/@angular/core/src/view/view.ts | 29 +-- .../core/test/application_ref_spec.ts | 52 +++-- .../linker/ng_container_integration_spec.ts | 1 - .../test/linker/security_integration_spec.ts | 1 - .../core/test/view/component_view_spec.ts | 4 +- .../@angular/core/test/view/element_spec.ts | 2 +- .../core/test/view/embedded_view_spec.ts | 12 +- modules/@angular/core/test/view/query_spec.ts | 40 ++-- .../language-service/src/language_service.ts | 5 +- .../language-service/src/typescript_host.ts | 2 +- .../src/browser/tools/common_tools.ts | 10 - .../src/browser/tools/tools.ts | 12 +- .../platform-browser/src/dom/dom_renderer.ts | 19 +- .../test/browser/bootstrap_spec.ts | 48 ++-- .../test/browser/tools/tools_spec.ts | 1 - 37 files changed, 527 insertions(+), 300 deletions(-) diff --git a/modules/@angular/common/test/directives/ng_class_spec.ts b/modules/@angular/common/test/directives/ng_class_spec.ts index cd5e242c6d..66b42e9a71 100644 --- a/modules/@angular/common/test/directives/ng_class_spec.ts +++ b/modules/@angular/common/test/directives/ng_class_spec.ts @@ -13,9 +13,14 @@ export function main() { describe('binding to CSS class list', () => { let fixture: ComponentFixture; + function normalizeClassNames(classes: string) { + return classes.trim().split(' ').sort().join(' '); + } + function detectChangesAndExpectClassName(classes: string): void { fixture.detectChanges(); - expect(fixture.debugElement.children[0].nativeElement.className.trim()).toEqual(classes); + let nonNormalizedClassName = fixture.debugElement.children[0].nativeElement.className; + expect(normalizeClassNames(nonNormalizedClassName)).toEqual(normalizeClassNames(classes)); } function getComponent(): TestComponent { return fixture.debugElement.componentInstance; } diff --git a/modules/@angular/compiler/src/aot/compiler_factory.ts b/modules/@angular/compiler/src/aot/compiler_factory.ts index 9975018128..3a5c002da0 100644 --- a/modules/@angular/compiler/src/aot/compiler_factory.ts +++ b/modules/@angular/compiler/src/aot/compiler_factory.ts @@ -68,9 +68,9 @@ export function createAotCompiler(compilerHost: AotCompilerHost, options: AotCom const expressionParser = new Parser(new Lexer()); const elementSchemaRegistry = new DomElementSchemaRegistry(); const tmplParser = - new TemplateParser(expressionParser, elementSchemaRegistry, htmlParser, console, []); + new TemplateParser(config, expressionParser, elementSchemaRegistry, htmlParser, console, []); const resolver = new CompileMetadataResolver( - new NgModuleResolver(staticReflector), new DirectiveResolver(staticReflector), + config, new NgModuleResolver(staticReflector), new DirectiveResolver(staticReflector), new PipeResolver(staticReflector), summaryResolver, elementSchemaRegistry, normalizer, symbolCache, staticReflector); // TODO(vicb): do not pass options.i18nFormat here diff --git a/modules/@angular/compiler/src/config.ts b/modules/@angular/compiler/src/config.ts index 1619006f2d..a257ebd08f 100644 --- a/modules/@angular/compiler/src/config.ts +++ b/modules/@angular/compiler/src/config.ts @@ -26,17 +26,19 @@ export class CompilerConfig { private _genDebugInfo: boolean; private _logBindingUpdate: boolean; public useJit: boolean; + public useViewEngine: boolean; public missingTranslation: MissingTranslationStrategy; constructor( {renderTypes = new DefaultRenderTypes(), defaultEncapsulation = ViewEncapsulation.Emulated, - genDebugInfo, logBindingUpdate, useJit = true, missingTranslation}: { + genDebugInfo, logBindingUpdate, useJit = true, missingTranslation, useViewEngine}: { renderTypes?: RenderTypes, defaultEncapsulation?: ViewEncapsulation, genDebugInfo?: boolean, logBindingUpdate?: boolean, useJit?: boolean, missingTranslation?: MissingTranslationStrategy, + useViewEngine?: boolean } = {}) { this.renderTypes = renderTypes; this.defaultEncapsulation = defaultEncapsulation; @@ -44,6 +46,7 @@ export class CompilerConfig { this._logBindingUpdate = logBindingUpdate; this.useJit = useJit; this.missingTranslation = missingTranslation; + this.useViewEngine = useViewEngine; } get genDebugInfo(): boolean { diff --git a/modules/@angular/compiler/src/directive_wrapper_compiler.ts b/modules/@angular/compiler/src/directive_wrapper_compiler.ts index fd53c161c3..8531b0f286 100644 --- a/modules/@angular/compiler/src/directive_wrapper_compiler.ts +++ b/modules/@angular/compiler/src/directive_wrapper_compiler.ts @@ -357,7 +357,8 @@ function parseHostBindings( const sourceSpan = new ParseSourceSpan( new ParseLocation(sourceFile, null, null, null), new ParseLocation(sourceFile, null, null, null)); - const parsedHostProps = parser.createDirectiveHostPropertyAsts(dirMeta.toSummary(), sourceSpan); + const parsedHostProps = + parser.createDirectiveHostPropertyAsts(dirMeta.toSummary(), dirMeta.selector, sourceSpan); const parsedHostListeners = parser.createDirectiveHostEventAsts(dirMeta.toSummary(), sourceSpan); return new ParseResult(parsedHostProps, parsedHostListeners, errors); diff --git a/modules/@angular/compiler/src/i18n/extractor.ts b/modules/@angular/compiler/src/i18n/extractor.ts index b3d6f7f7d6..933bd30356 100644 --- a/modules/@angular/compiler/src/i18n/extractor.ts +++ b/modules/@angular/compiler/src/i18n/extractor.ts @@ -107,7 +107,7 @@ export class Extractor { {get: (url: string) => host.loadResource(url)}, urlResolver, htmlParser, config); const elementSchemaRegistry = new DomElementSchemaRegistry(); const resolver = new CompileMetadataResolver( - new NgModuleResolver(staticReflector), new DirectiveResolver(staticReflector), + config, new NgModuleResolver(staticReflector), new DirectiveResolver(staticReflector), new PipeResolver(staticReflector), summaryResolver, elementSchemaRegistry, normalizer, symbolCache, staticReflector); diff --git a/modules/@angular/compiler/src/jit/compiler.ts b/modules/@angular/compiler/src/jit/compiler.ts index e1d7a5af75..de4c783e6d 100644 --- a/modules/@angular/compiler/src/jit/compiler.ts +++ b/modules/@angular/compiler/src/jit/compiler.ts @@ -11,7 +11,7 @@ import {Compiler, ComponentFactory, ComponentRenderTypeV2, Inject, Injector, Mod 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, USE_VIEW_ENGINE} from '../config'; +import {CompilerConfig} from '../config'; import {DirectiveWrapperCompiler} from '../directive_wrapper_compiler'; import {stringify} from '../facade/lang'; import {CompilerInjectable} from '../injectable'; @@ -50,8 +50,7 @@ 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, - @Inject(USE_VIEW_ENGINE) private _useViewEngine: boolean) {} + private _compilerConfig: CompilerConfig, private _animationParser: AnimationParser) {} get injector(): Injector { return this._injector; } @@ -245,7 +244,7 @@ export class JitCompiler implements Compiler { private _compileDirectiveWrapper( dirMeta: CompileDirectiveMetadata, moduleMeta: CompileNgModuleMetadata): void { - if (this._useViewEngine) { + if (this._compilerConfig.useViewEngine) { return; } const compileResult = this._directiveWrapperCompiler.compile(dirMeta); diff --git a/modules/@angular/compiler/src/jit/compiler_factory.ts b/modules/@angular/compiler/src/jit/compiler_factory.ts index 4d04a46c21..e83d5cabbc 100644 --- a/modules/@angular/compiler/src/jit/compiler_factory.ts +++ b/modules/@angular/compiler/src/jit/compiler_factory.ts @@ -43,9 +43,8 @@ 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); +function viewCompilerFactory(cc: CompilerConfig, sr: ElementSchemaRegistry) { + return cc.useViewEngine ? new ViewCompilerNext(cc, sr) : new ViewCompiler(cc, sr); } /** @@ -91,7 +90,7 @@ export const COMPILER_PROVIDERS: Array|{[k: string]: any}|any[]> = { provide: ViewCompiler, useFactory: viewCompilerFactory, - deps: [USE_VIEW_ENGINE, CompilerConfig, ElementSchemaRegistry] + deps: [CompilerConfig, ElementSchemaRegistry] }, NgModuleCompiler, DirectiveWrapperCompiler, @@ -124,7 +123,7 @@ export class JitCompilerFactory implements CompilerFactory { const injector = ReflectiveInjector.resolveAndCreate([ COMPILER_PROVIDERS, { provide: CompilerConfig, - useFactory: () => { + useFactory: (useViewEngine: boolean) => { return new CompilerConfig({ // let explicit values from the compiler options overwrite options // from the app providers. E.g. important for the testing platform. @@ -136,10 +135,10 @@ export class JitCompilerFactory implements CompilerFactory { // from the app providers defaultEncapsulation: opts.defaultEncapsulation, logBindingUpdate: opts.useDebug, - missingTranslation: opts.missingTranslation, + missingTranslation: opts.missingTranslation, useViewEngine }); }, - deps: [] + deps: [USE_VIEW_ENGINE] }, opts.providers ]); diff --git a/modules/@angular/compiler/src/metadata_resolver.ts b/modules/@angular/compiler/src/metadata_resolver.ts index c628ebdff1..340e18146b 100644 --- a/modules/@angular/compiler/src/metadata_resolver.ts +++ b/modules/@angular/compiler/src/metadata_resolver.ts @@ -12,7 +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 {CompilerConfig} from './config'; import {DirectiveNormalizer} from './directive_normalizer'; import {DirectiveResolver} from './directive_resolver'; import {stringify} from './facade/lang'; @@ -48,14 +48,14 @@ export class CompileMetadataResolver { private _ngModuleOfTypes = new Map, Type>(); constructor( - private _ngModuleResolver: NgModuleResolver, private _directiveResolver: DirectiveResolver, - private _pipeResolver: PipeResolver, private _summaryResolver: SummaryResolver, + private _config: CompilerConfig, private _ngModuleResolver: NgModuleResolver, + private _directiveResolver: DirectiveResolver, private _pipeResolver: PipeResolver, + private _summaryResolver: SummaryResolver, private _schemaRegistry: ElementSchemaRegistry, private _directiveNormalizer: DirectiveNormalizer, @Optional() private _staticSymbolCache: StaticSymbolCache, private _reflector: ReflectorReader = reflector, - @Optional() @Inject(ERROR_COLLECTOR_TOKEN) private _errorCollector?: ErrorCollector, - @Optional() @Inject(USE_VIEW_ENGINE) private _useViewEngine?: boolean) {} + @Optional() @Inject(ERROR_COLLECTOR_TOKEN) private _errorCollector?: ErrorCollector) {} clearCacheFor(type: Type) { const dirMeta = this._directiveCache.get(type); @@ -148,7 +148,7 @@ export class CompileMetadataResolver { ngfactoryFilePath(dirType.filePath), cpl.componentFactoryName(dirType)); } else { const hostView = this.getHostComponentViewClass(dirType); - if (this._useViewEngine) { + if (this._config.useViewEngine) { return viewEngine.createComponentFactory(selector, dirType, hostView); } else { return new ComponentFactory(selector, hostView, dirType); diff --git a/modules/@angular/compiler/src/template_parser/binding_parser.ts b/modules/@angular/compiler/src/template_parser/binding_parser.ts index f45151a3d8..8544a4ce9e 100644 --- a/modules/@angular/compiler/src/template_parser/binding_parser.ts +++ b/modules/@angular/compiler/src/template_parser/binding_parser.ts @@ -62,8 +62,9 @@ export class BindingParser { getUsedPipes(): CompilePipeSummary[] { return Array.from(this._usedPipes.values()); } - createDirectiveHostPropertyAsts(dirMeta: CompileDirectiveSummary, sourceSpan: ParseSourceSpan): - BoundElementPropertyAst[] { + createDirectiveHostPropertyAsts( + dirMeta: CompileDirectiveSummary, elementSelector: string, + sourceSpan: ParseSourceSpan): BoundElementPropertyAst[] { if (dirMeta.hostProperties) { const boundProps: BoundProperty[] = []; Object.keys(dirMeta.hostProperties).forEach(propName => { @@ -76,7 +77,7 @@ export class BindingParser { sourceSpan); } }); - return boundProps.map((prop) => this.createElementPropertyAst(dirMeta.selector, prop)); + return boundProps.map((prop) => this.createElementPropertyAst(elementSelector, prop)); } } diff --git a/modules/@angular/compiler/src/template_parser/template_parser.ts b/modules/@angular/compiler/src/template_parser/template_parser.ts index 002d4a8d13..23dbe16ac4 100644 --- a/modules/@angular/compiler/src/template_parser/template_parser.ts +++ b/modules/@angular/compiler/src/template_parser/template_parser.ts @@ -9,6 +9,7 @@ import {Inject, InjectionToken, Optional, SchemaMetadata} from '@angular/core'; import {CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeSummary, CompileTemplateSummary, CompileTokenMetadata, CompileTypeMetadata, identifierName} from '../compile_metadata'; +import {CompilerConfig} from '../config'; import {AST, ASTWithSource, EmptyExpr} from '../expression_parser/ast'; import {Parser} from '../expression_parser/parser'; import {isPresent} from '../facade/lang'; @@ -33,6 +34,7 @@ import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventA import {PreparsedElementType, preparseElement} from './template_preparser'; + // Group 1 = "bind-" // Group 2 = "let-" // Group 3 = "ref-/#" @@ -88,8 +90,9 @@ export class TemplateParseResult { @CompilerInjectable() export class TemplateParser { constructor( - private _exprParser: Parser, private _schemaRegistry: ElementSchemaRegistry, - private _htmlParser: I18NHtmlParser, private _console: Console, + private _config: CompilerConfig, private _exprParser: Parser, + private _schemaRegistry: ElementSchemaRegistry, private _htmlParser: I18NHtmlParser, + private _console: Console, @Optional() @Inject(TEMPLATE_TRANSFORMS) public transforms: TemplateAstVisitor[]) {} parse( @@ -144,8 +147,8 @@ export class TemplateParser { const bindingParser = new BindingParser( this._exprParser, interpolationConfig, this._schemaRegistry, uniqPipes, errors); const parseVisitor = new TemplateParseVisitor( - providerViewContext, uniqDirectives, bindingParser, this._schemaRegistry, schemas, - errors); + this._config, providerViewContext, uniqDirectives, bindingParser, this._schemaRegistry, + schemas, errors); result = html.visitAll(parseVisitor, htmlAstWithErrors.rootNodes, EMPTY_ELEMENT_CONTEXT); errors.push(...providerViewContext.errors); usedPipes.push(...bindingParser.getUsedPipes()); @@ -211,9 +214,10 @@ class TemplateParseVisitor implements html.Visitor { contentQueryStartId: number; constructor( - public providerViewContext: ProviderViewContext, directives: CompileDirectiveSummary[], - private _bindingParser: BindingParser, private _schemaRegistry: ElementSchemaRegistry, - private _schemas: SchemaMetadata[], private _targetErrors: TemplateParseError[]) { + private config: CompilerConfig, public providerViewContext: ProviderViewContext, + directives: CompileDirectiveSummary[], private _bindingParser: BindingParser, + private _schemaRegistry: ElementSchemaRegistry, private _schemas: SchemaMetadata[], + private _targetErrors: TemplateParseError[]) { // Note: queries start with id 1 so we can use the number in a Bloom filter! this.contentQueryStartId = providerViewContext.component.viewQueries.length + 1; directives.forEach((directive, index) => { @@ -574,8 +578,8 @@ class TemplateParseVisitor implements html.Visitor { component = directive; } const directiveProperties: BoundDirectivePropertyAst[] = []; - let hostProperties = - this._bindingParser.createDirectiveHostPropertyAsts(directive, sourceSpan); + let hostProperties = this._bindingParser.createDirectiveHostPropertyAsts( + directive, this.config.useViewEngine ? elementName : directive.selector, sourceSpan); // Note: We need to check the host properties here as well, // as we don't know the element name in the DirectiveWrapperCompiler yet. hostProperties = this._checkPropertiesInSchema(elementName, hostProperties); diff --git a/modules/@angular/compiler/src/view_compiler_next/view_compiler.ts b/modules/@angular/compiler/src/view_compiler_next/view_compiler.ts index 649f9f9211..96bbdd3901 100644 --- a/modules/@angular/compiler/src/view_compiler_next/view_compiler.ts +++ b/modules/@angular/compiler/src/view_compiler_next/view_compiler.ts @@ -61,13 +61,14 @@ export class ViewCompilerNext extends ViewCompiler { const viewBuilderFactory = (parent: ViewBuilder): ViewBuilder => { const embeddedViewIndex = embeddedViewCount++; const viewName = viewClassName(component.type.reference, embeddedViewIndex); - return new ViewBuilder(parent, viewName, usedPipes, staticQueryIds, viewBuilderFactory); + return new ViewBuilder( + parent, component, viewName, usedPipes, staticQueryIds, viewBuilderFactory); }; const visitor = viewBuilderFactory(null); visitor.visitAll([], template); - statements.push(...visitor.build(component)); + statements.push(...visitor.build()); return new ViewCompileResult(statements, visitor.viewName, renderComponentVar.name, []); } @@ -107,7 +108,8 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter private handleEventExpressions: HandleEventExpression[] = []; constructor( - private parent: ViewBuilder, public viewName: string, private usedPipes: CompilePipeSummary[], + private parent: ViewBuilder, private component: CompileDirectiveMetadata, + public viewName: string, private usedPipes: CompilePipeSummary[], private staticQueryIds: Map, private viewBuilderFactory: ViewBuilderFactory) {} @@ -122,6 +124,25 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter }); } + if (!this.parent) { + const queryIds = staticViewQueryIds(this.staticQueryIds); + this.component.viewQueries.forEach((query, queryIndex) => { + // Note: queries start with id 1 so we can use the number in a Bloom filter! + const queryId = queryIndex + 1; + const bindingType = + query.first ? viewEngine.QueryBindingType.First : viewEngine.QueryBindingType.All; + let flags = viewEngine.NodeFlags.HasViewQuery; + if (queryIds.staticQueryIds.has(queryId)) { + flags |= viewEngine.NodeFlags.HasStaticQuery; + } else { + flags |= viewEngine.NodeFlags.HasDynamicQuery; + } + this.nodeDefs.push(o.importExpr(createIdentifier(Identifiers.queryDef)).callFn([ + o.literal(flags), o.literal(queryId), + new o.LiteralMapExpr([new o.LiteralMapEntry(query.propertyName, o.literal(bindingType))]) + ])); + }); + } templateVisitAll(this, astNodes); if (astNodes.length === 0 || (this.parent && needsAdditionalRootNode(astNodes[astNodes.length - 1]))) { @@ -133,9 +154,9 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter } } - build(component: CompileDirectiveMetadata, targetStatements: o.Statement[] = []): o.Statement[] { - const compType = o.importType(component.type); - this.children.forEach((child) => { child.build(component, targetStatements); }); + build(targetStatements: o.Statement[] = []): o.Statement[] { + const compType = o.importType(this.component.type); + this.children.forEach((child) => child.build(targetStatements)); const updateDirectivesFn = this._createUpdateFn(this.updateDirectivesExpressions, compType); const updateRendererFn = this._createUpdateFn(this.updateRendererExpressions, compType); @@ -174,7 +195,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter } let viewFlags = viewEngine.ViewFlags.None; - if (!this.parent && component.changeDetection === ChangeDetectionStrategy.OnPush) { + if (!this.parent && this.component.changeDetection === ChangeDetectionStrategy.OnPush) { viewFlags |= viewEngine.ViewFlags.OnPush; } const viewFactory = new o.DeclareFunctionStmt( @@ -272,31 +293,36 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter // reserve the space in the nodeDefs array so we can add children this.nodeDefs.push(null); - let {flags, usedEvents, queryMatchesExpr, hostBindings} = - this._visitElementOrTemplate(nodeIndex, ast); - - templateVisitAll(this, ast.children); - - ast.inputs.forEach( - (inputAst) => { hostBindings.push({context: COMP_VAR, value: inputAst.value}); }); - this._addUpdateExpressions(nodeIndex, hostBindings, this.updateRendererExpressions); - - 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); - }); - - const childCount = this.nodeDefs.length - nodeIndex - 1; - let elName = ast.name; if (ast.name === NG_CONTAINER_TAG) { // Using a null element name creates an anchor. elName = null; } + let {flags, usedEvents, queryMatchesExpr, hostBindings} = + this._visitElementOrTemplate(nodeIndex, ast); + + let inputDefs: o.Expression[] = []; + let outputDefs: o.Expression[] = []; + if (elName) { + ast.inputs.forEach( + (inputAst) => { hostBindings.push({context: COMP_VAR, value: inputAst.value}); }); + if (hostBindings.length) { + this._addUpdateExpressions(nodeIndex, hostBindings, this.updateRendererExpressions); + } + inputDefs = elementBindingDefs(ast.inputs); + ast.directives.forEach( + (dirAst, dirIndex) => inputDefs.push(...elementBindingDefs(dirAst.hostProperties))); + outputDefs = usedEvents.map(([target, eventName]) => { + return target ? o.literalArr([o.literal(target), o.literal(eventName)]) : + o.literal(eventName); + }); + } + + templateVisitAll(this, ast.children); + + const childCount = this.nodeDefs.length - nodeIndex - 1; + // elementDef( // flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number, // childCount: number, name: string, fixedAttrs: {[name: string]: string} = {}, @@ -307,7 +333,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter // outputs?: (string | [string, string])[]): NodeDef; this.nodeDefs[nodeIndex] = o.importExpr(createIdentifier(Identifiers.elementDef)).callFn([ o.literal(flags), queryMatchesExpr, o.literal(ast.ngContentIndex), o.literal(childCount), - o.literal(elName), fixedAttrsDef(ast), + o.literal(elName), elName ? fixedAttrsDef(ast) : o.NULL_EXPR, inputDefs.length ? o.literalArr(inputDefs) : o.NULL_EXPR, outputDefs.length ? o.literalArr(outputDefs) : o.NULL_EXPR ]); @@ -422,22 +448,6 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter // reserve the space in the nodeDefs array so we can add children this.nodeDefs.push(null); - directiveAst.directive.viewQueries.forEach((query, queryIndex) => { - // Note: queries start with id 1 so we can use the number in a Bloom filter! - const queryId = queryIndex + 1; - const bindingType = - query.first ? viewEngine.QueryBindingType.First : viewEngine.QueryBindingType.All; - let flags = viewEngine.NodeFlags.HasViewQuery; - if (queryIds.staticQueryIds.has(queryId)) { - flags |= viewEngine.NodeFlags.HasStaticQuery; - } else { - flags |= viewEngine.NodeFlags.HasDynamicQuery; - } - this.nodeDefs.push(o.importExpr(createIdentifier(Identifiers.queryDef)).callFn([ - o.literal(flags), o.literal(queryId), - new o.LiteralMapExpr([new o.LiteralMapEntry(query.propertyName, o.literal(bindingType))]) - ])); - }); directiveAst.directive.queries.forEach((query, queryIndex) => { let flags = viewEngine.NodeFlags.HasContentQuery; const queryId = directiveAst.contentQueryStartId + queryIndex; @@ -493,7 +503,8 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter outputDefs.push(new o.LiteralMapEntry(propName, o.literal(eventName), false)); } }); - if (directiveAst.inputs.length) { + if (directiveAst.inputs.length || + (flags & (viewEngine.NodeFlags.DoCheck | viewEngine.NodeFlags.OnInit)) > 0) { this._addUpdateExpressions( nodeIndex, directiveAst.inputs.map((input) => { return {context: COMP_VAR, value: input.value}; }), @@ -697,9 +708,6 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter private _addUpdateExpressions( nodeIndex: number, expressions: {context: o.Expression, value: AST}[], target: UpdateExpression[]) { - if (expressions.length === 0) { - return; - } const transformedExpressions = expressions.map(({context, value}) => { if (value instanceof ASTWithSource) { value = value.ast; @@ -885,7 +893,7 @@ function elementBindingDefs(inputAsts: BoundElementPropertyAst[]): o.Expression[ } -function fixedAttrsDef(elementAst: ElementAst): o.LiteralMapExpr { +function fixedAttrsDef(elementAst: ElementAst): o.Expression { const mapResult: {[key: string]: string} = {}; elementAst.attrs.forEach(attrAst => { mapResult[attrAst.name] = attrAst.value; }); elementAst.directives.forEach(dirAst => { @@ -898,10 +906,8 @@ function fixedAttrsDef(elementAst: ElementAst): o.LiteralMapExpr { 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); + return o.literalArr(Object.keys(mapResult).sort().map( + (attrName) => o.literalArr([o.literal(attrName), o.literal(mapResult[attrName])]))); } function mergeAttributeValue(attrName: string, attrValue1: string, attrValue2: string): string { @@ -967,9 +973,21 @@ function findStaticQueryIds( return result; } +function staticViewQueryIds(nodeStaticQueryIds: Map): + StaticAndDynamicQueryIds { + const staticQueryIds = new Set(); + const dynamicQueryIds = new Set(); + Array.from(nodeStaticQueryIds.values()).forEach((entry) => { + entry.staticQueryIds.forEach(queryId => staticQueryIds.add(queryId)); + entry.dynamicQueryIds.forEach(queryId => dynamicQueryIds.add(queryId)); + }); + dynamicQueryIds.forEach(queryId => staticQueryIds.delete(queryId)); + return {staticQueryIds, dynamicQueryIds}; +} + function createComponentFactoryResolver(directives: DirectiveAst[]): ProviderAst { const componentDirMeta = directives.find(dirAst => dirAst.directive.isComponent); - if (componentDirMeta) { + if (componentDirMeta && componentDirMeta.directive.entryComponents.length) { const entryComponentFactories = componentDirMeta.directive.entryComponents.map( (entryComponent) => o.importExpr({reference: entryComponent.componentFactory})); const cfrExpr = o.importExpr(createIdentifier(Identifiers.CodegenComponentFactoryResolver)) @@ -988,4 +1006,4 @@ function createComponentFactoryResolver(directives: DirectiveAst[]): ProviderAst ProviderAstType.PrivateService, [], componentDirMeta.sourceSpan); } return null; -} \ No newline at end of file +} diff --git a/modules/@angular/core/src/linker/component_factory.ts b/modules/@angular/core/src/linker/component_factory.ts index 9b02424c4a..bce6de4684 100644 --- a/modules/@angular/core/src/linker/component_factory.ts +++ b/modules/@angular/core/src/linker/component_factory.ts @@ -92,8 +92,12 @@ export class ComponentRef_ extends ComponentRef { * @stable */ export class ComponentFactory { - /** @internal */ - _viewClass: Type>; + /** + * TODO(tbosch): type this properly to ViewDefinitionFactory again once the view engine + * is the default. + * @internal + */ + _viewClass: any; constructor( public selector: string, _viewClass: Type>, public componentType: Type) { this._viewClass = _viewClass; diff --git a/modules/@angular/core/src/view/element.ts b/modules/@angular/core/src/view/element.ts index 714cfce819..0fdf322919 100644 --- a/modules/@angular/core/src/view/element.ts +++ b/modules/@angular/core/src/view/element.ts @@ -10,7 +10,7 @@ import {isDevMode} from '../application_ref'; import {SecurityContext} from '../security'; import {BindingDef, BindingType, DebugContext, DisposableFn, ElementData, ElementOutputDef, NodeData, NodeDef, NodeFlags, NodeType, QueryValueType, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, asElementData} from './types'; -import {checkAndUpdateBinding, dispatchEvent, elementEventFullName, filterQueryId, getParentRenderElement, resolveViewDefinition, sliceErrorStack, splitMatchedQueriesDsl} from './util'; +import {checkAndUpdateBinding, dispatchEvent, elementEventFullName, filterQueryId, getParentRenderElement, resolveViewDefinition, sliceErrorStack, splitMatchedQueriesDsl, splitNamespace} from './util'; export function anchorDef( flags: NodeFlags, matchedQueriesDsl: [string | number, QueryValueType][], @@ -36,6 +36,7 @@ export function anchorDef( bindings: [], disposableCount: 0, element: { + ns: undefined, name: undefined, attrs: undefined, outputs: [], template, source, @@ -54,8 +55,8 @@ export function anchorDef( export function elementDef( flags: NodeFlags, matchedQueriesDsl: [string | number, QueryValueType][], - ngContentIndex: number, childCount: number, name: string, - fixedAttrs: {[name: string]: string} = {}, + ngContentIndex: number, childCount: number, namespaceAndName: string, + fixedAttrs: [string, string][] = [], bindings?: ([BindingType.ElementClass, string] | [BindingType.ElementStyle, string, string] | [BindingType.ElementAttribute | BindingType.ElementProperty, string, SecurityContext])[], @@ -63,13 +64,18 @@ export function elementDef( // skip the call to sliceErrorStack itself + the call to this function. const source = isDevMode() ? sliceErrorStack(2, 3) : ''; const {matchedQueries, references, matchedQueryIds} = splitMatchedQueriesDsl(matchedQueriesDsl); + let ns: string; + let name: string; + if (namespaceAndName) { + [ns, name] = splitNamespace(namespaceAndName); + } bindings = bindings || []; const bindingDefs: BindingDef[] = new Array(bindings.length); for (let i = 0; i < bindings.length; i++) { const entry = bindings[i]; let bindingDef: BindingDef; const bindingType = entry[0]; - const name = entry[1]; + const [ns, name] = splitNamespace(entry[1]); let securityContext: SecurityContext; let suffix: string; switch (bindingType) { @@ -81,7 +87,7 @@ export function elementDef( securityContext = entry[2]; break; } - bindingDefs[i] = {type: bindingType, name, nonMinifiedName: name, securityContext, suffix}; + bindingDefs[i] = {type: bindingType, ns, name, nonMinifiedName: name, securityContext, suffix}; } outputs = outputs || []; const outputDefs: ElementOutputDef[] = new Array(outputs.length); @@ -96,6 +102,11 @@ export function elementDef( } outputDefs[i] = {eventName: eventName, target: target}; } + fixedAttrs = fixedAttrs || []; + const attrs = <[string, string, string][]>fixedAttrs.map(([namespaceAndName, value]) => { + const [ns, name] = splitNamespace(namespaceAndName); + return [ns, name, value]; + }); return { type: NodeType.Element, // will bet set by the view definition @@ -112,8 +123,9 @@ export function elementDef( bindings: bindingDefs, disposableCount: outputDefs.length, element: { + ns, name, - attrs: fixedAttrs, + attrs, outputs: outputDefs, source, template: undefined, // will bet set by the view definition @@ -136,9 +148,7 @@ export function createElement(view: ViewData, renderHost: any, def: NodeDef): El let el: any; if (view.parent || !rootSelectorOrNode) { if (elDef.name) { - // TODO(vicb): move the namespace to the node definition - const nsAndName = splitNamespace(elDef.name); - el = renderer.createElement(nsAndName[1], nsAndName[0]); + el = renderer.createElement(elDef.name, elDef.ns); } else { el = renderer.createComment(''); } @@ -150,10 +160,9 @@ export function createElement(view: ViewData, renderHost: any, def: NodeDef): El el = renderer.selectRootElement(rootSelectorOrNode); } if (elDef.attrs) { - for (let attrName in elDef.attrs) { - // TODO(vicb): move the namespace to the node definition - const nsAndName = splitNamespace(attrName); - renderer.setAttribute(el, nsAndName[1], elDef.attrs[attrName], nsAndName[0]); + for (let i = 0; i < elDef.attrs.length; i++) { + const [ns, name, value] = elDef.attrs[i]; + renderer.setAttribute(el, name, value, ns); } } if (elDef.outputs.length) { @@ -217,11 +226,11 @@ function checkAndUpdateElementValue(view: ViewData, def: NodeDef, bindingIdx: nu return; } const binding = def.bindings[bindingIdx]; - const name = binding.name; const renderNode = asElementData(view, def.index).renderElement; + const name = binding.name; switch (binding.type) { case BindingType.ElementAttribute: - setElementAttribute(view, binding, renderNode, name, value); + setElementAttribute(view, binding, renderNode, binding.ns, name, value); break; case BindingType.ElementClass: setElementClass(view, renderNode, name, value); @@ -236,17 +245,15 @@ function checkAndUpdateElementValue(view: ViewData, def: NodeDef, bindingIdx: nu } function setElementAttribute( - view: ViewData, binding: BindingDef, renderNode: any, name: string, value: any) { + view: ViewData, binding: BindingDef, renderNode: any, ns: string, name: string, value: any) { const securityContext = binding.securityContext; let renderValue = securityContext ? view.root.sanitizer.sanitize(securityContext, value) : value; renderValue = renderValue != null ? renderValue.toString() : null; const renderer = view.renderer; - // TODO(vicb): move the namespace to the node definition - const nsAndName = splitNamespace(name); if (value != null) { - renderer.setAttribute(renderNode, nsAndName[1], renderValue, nsAndName[0]); + renderer.setAttribute(renderNode, name, renderValue, ns); } else { - renderer.removeAttribute(renderNode, nsAndName[1], nsAndName[0]); + renderer.removeAttribute(renderNode, name, ns); } } @@ -285,13 +292,3 @@ function setElementProperty( let renderValue = securityContext ? view.root.sanitizer.sanitize(securityContext, value) : value; view.renderer.setProperty(renderNode, name, renderValue); } - -const NS_PREFIX_RE = /^:([^:]+):(.+)$/; - -function splitNamespace(name: string): string[] { - if (name[0] === ':') { - const match = name.match(NS_PREFIX_RE); - return [match[1], match[2]]; - } - return ['', name]; -} diff --git a/modules/@angular/core/src/view/index.ts b/modules/@angular/core/src/view/index.ts index c8c3e3d144..94d992ec21 100644 --- a/modules/@angular/core/src/view/index.ts +++ b/modules/@angular/core/src/view/index.ts @@ -11,10 +11,10 @@ export {ngContentDef} from './ng_content'; export {directiveDef, pipeDef, providerDef} from './provider'; export {pureArrayDef, pureObjectDef, purePipeDef} from './pure_expression'; export {queryDef} from './query'; -export {createComponentFactory} from './refs'; +export {ViewRef_, createComponentFactory, nodeValue} from './refs'; export {initServicesIfNeeded} from './services'; export {textDef} from './text'; -export {createComponentRenderTypeV2, elementEventFullName, nodeValue, rootRenderNodes, unwrapValue} from './util'; +export {createComponentRenderTypeV2, elementEventFullName, rootRenderNodes, unwrapValue} 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 b4a71df864..40adccc323 100644 --- a/modules/@angular/core/src/view/provider.ts +++ b/modules/@angular/core/src/view/provider.ts @@ -12,9 +12,9 @@ import {ElementRef} from '../linker/element_ref'; import {TemplateRef} from '../linker/template_ref'; import {ViewContainerRef} from '../linker/view_container_ref'; import {ViewEncapsulation} from '../metadata/view'; -import {ComponentRenderTypeV2, RenderComponentType as RenderComponentTypeV1, Renderer as RendererV1, RendererFactoryV2, RendererV2, RootRenderer as RootRendererV1} from '../render/api'; +import {ComponentRenderTypeV2, Renderer as RendererV1, RendererFactoryV2, RendererV2} from '../render/api'; -import {createChangeDetectorRef, createInjector, createTemplateRef, createViewContainerRef} from './refs'; +import {createChangeDetectorRef, createInjector, createRendererV1, createTemplateRef, createViewContainerRef} from './refs'; import {BindingDef, BindingType, DepDef, DepFlags, DirectiveOutputDef, DisposableFn, NodeData, NodeDef, NodeFlags, NodeType, ProviderData, ProviderType, QueryBindingType, QueryDef, QueryValueType, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewState, asElementData, asProviderData} from './types'; import {checkAndUpdateBinding, dispatchEvent, filterQueryId, isComponentView, splitMatchedQueriesDsl, tokenKey, viewParentEl} from './util'; @@ -40,6 +40,7 @@ export function directiveDef( bindings[bindingIndex] = { type: BindingType.DirectiveProperty, name: prop, nonMinifiedName, + ns: undefined, securityContext: undefined, suffix: undefined }; @@ -337,14 +338,7 @@ export function resolveDep( switch (tokenKey) { case RendererV1TokenKey: { const compView = findCompView(view, elDef, allowPrivateServices); - const compDef = compView.parentNodeDef; - const rootRendererV1: RootRendererV1 = view.root.injector.get(RootRendererV1); - - // Note: Don't fill in the styles as they have been installed already via the RendererV2! - const compRenderType = compDef.provider.componentRenderType; - return rootRendererV1.renderComponent(new RenderComponentTypeV1( - compRenderType ? compRenderType.id : '0', '', 0, - compRenderType ? compRenderType.encapsulation : ViewEncapsulation.None, [], {})); + return createRendererV1(compView); } case RendererV2TokenKey: { const compView = findCompView(view, elDef, allowPrivateServices); diff --git a/modules/@angular/core/src/view/pure_expression.ts b/modules/@angular/core/src/view/pure_expression.ts index 318dd1c5a8..46cddd82c5 100644 --- a/modules/@angular/core/src/view/pure_expression.ts +++ b/modules/@angular/core/src/view/pure_expression.ts @@ -29,6 +29,7 @@ function _pureExpressionDef(type: PureExpressionType, propertyNames: string[]): bindings[i] = { type: BindingType.PureExpressionProperty, name: prop, + ns: undefined, nonMinifiedName: prop, securityContext: undefined, suffix: undefined diff --git a/modules/@angular/core/src/view/query.ts b/modules/@angular/core/src/view/query.ts index f86b3e701a..4a6e734a02 100644 --- a/modules/@angular/core/src/view/query.ts +++ b/modules/@angular/core/src/view/query.ts @@ -80,14 +80,14 @@ export function dirtyParentQueries(view: ViewData) { } // view queries - let compDef = view.parentNodeDef; - view = view.parent; - if (view) { - for (let i = compDef.index + 1; i <= compDef.index + compDef.childCount; i++) { + if (view.def.nodeFlags & NodeFlags.HasViewQuery) { + for (let i = 0; i < view.def.nodes.length; i++) { const nodeDef = view.def.nodes[i]; if ((nodeDef.flags & NodeFlags.HasViewQuery) && (nodeDef.flags & NodeFlags.HasDynamicQuery)) { asQueryList(view, i).setDirty(); } + // only visit the root nodes + i += nodeDef.childCount; } } } @@ -97,16 +97,16 @@ export function checkAndUpdateQuery(view: ViewData, nodeDef: NodeDef) { if (!queryList.dirty) { return; } - const providerDef = nodeDef.parent; - const providerData = asProviderData(view, providerDef.index); + let directiveInstance: any; let newValues: any[]; if (nodeDef.flags & NodeFlags.HasContentQuery) { - const elementDef = providerDef.parent; + const elementDef = nodeDef.parent.parent; newValues = calcQueryValues( view, elementDef.index, elementDef.index + elementDef.childCount, nodeDef.query, []); + directiveInstance = asProviderData(view, nodeDef.parent.index).instance; } else if (nodeDef.flags & NodeFlags.HasViewQuery) { - const compView = providerData.componentView; - newValues = calcQueryValues(compView, 0, compView.def.nodes.length - 1, nodeDef.query, []); + newValues = calcQueryValues(view, 0, view.def.nodes.length - 1, nodeDef.query, []); + directiveInstance = view.component; } queryList.reset(newValues); const bindings = nodeDef.query.bindings; @@ -123,7 +123,7 @@ export function checkAndUpdateQuery(view: ViewData, nodeDef: NodeDef) { notify = true; break; } - providerData.instance[binding.propName] = boundValue; + directiveInstance[binding.propName] = boundValue; } if (notify) { queryList.notifyOnChanges(); diff --git a/modules/@angular/core/src/view/refs.ts b/modules/@angular/core/src/view/refs.ts index 94d744dd33..89c3bac280 100644 --- a/modules/@angular/core/src/view/refs.ts +++ b/modules/@angular/core/src/view/refs.ts @@ -6,6 +6,8 @@ * found in the LICENSE file at https://angular.io/license */ +import {NoOpAnimationPlayer} from '../animation/animation_player'; +import {ApplicationRef} from '../application_ref'; import {ChangeDetectorRef} from '../change_detection/change_detection'; import {Injector} from '../di'; import {ComponentFactory, ComponentRef} from '../linker/component_factory'; @@ -13,10 +15,12 @@ import {ElementRef} from '../linker/element_ref'; import {TemplateRef} from '../linker/template_ref'; import {ViewContainerRef} from '../linker/view_container_ref'; import {EmbeddedViewRef, ViewRef} from '../linker/view_ref'; +import {Renderer as RendererV1, RendererV2} from '../render/api'; import {Type} from '../type'; +import {VERSION} from '../version'; -import {ArgumentType, BindingType, DebugContext, DepFlags, ElementData, NodeCheckFn, NodeData, NodeDef, NodeFlags, NodeType, RootData, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewState, asElementData, asProviderData} from './types'; -import {isComponentView, renderNode, resolveViewDefinition, rootRenderNodes, tokenKey, viewParentEl} from './util'; +import {ArgumentType, BindingType, DebugContext, DepFlags, ElementData, NodeCheckFn, NodeData, NodeDef, NodeFlags, NodeType, RootData, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewState, asElementData, asProviderData, asTextData} from './types'; +import {isComponentView, renderNode, resolveViewDefinition, rootRenderNodes, splitNamespace, tokenKey, viewParentEl} from './util'; const EMPTY_CONTEXT = new Object(); @@ -26,16 +30,9 @@ export function createComponentFactory( return new ComponentFactory_(selector, componentType, viewDefFactory); } -class ComponentFactory_ implements ComponentFactory { - /** - * We are not renaming this field as the old ComponentFactory is using it. - * @internal */ - _viewClass: any; - - constructor( - public selector: string, public componentType: Type, - _viewDefFactory: ViewDefinitionFactory) { - this._viewClass = _viewDefFactory; +class ComponentFactory_ extends ComponentFactory { + constructor(selector: string, componentType: Type, viewDefFactory: ViewDefinitionFactory) { + super(selector, viewDefFactory, componentType); } /** @@ -49,13 +46,16 @@ class ComponentFactory_ implements ComponentFactory { const view = Services.createRootView( injector, projectableNodes || [], rootSelectorOrNode, viewDef, EMPTY_CONTEXT); const component = asProviderData(view, componentNodeIndex).instance; + view.renderer.setAttribute(asElementData(view, 0).renderElement, 'ng-version', VERSION.full); + return new ComponentRef_(view, new ViewRef_(view), component); } } -class ComponentRef_ implements ComponentRef { +class ComponentRef_ extends ComponentRef { private _elDef: NodeDef; constructor(private _view: ViewData, private _viewRef: ViewRef, private _component: any) { + super(); this._elDef = this._view.def.nodes[0]; } get location(): ElementRef { @@ -103,7 +103,11 @@ class ViewContainerRef_ implements ViewContainerRef { } } - get(index: number): ViewRef { return new ViewRef_(this._data.embeddedViews[index]); } + get(index: number): ViewRef { + const ref = new ViewRef_(this._data.embeddedViews[index]); + ref.attachToViewContainerRef(this); + return ref; + } get length(): number { return this._data.embeddedViews.length; }; @@ -124,8 +128,10 @@ class ViewContainerRef_ implements ViewContainerRef { } insert(viewRef: ViewRef, index?: number): ViewRef { - const viewData = (viewRef)._view; + const viewRef_ = viewRef; + const viewData = viewRef_._view; Services.attachEmbeddedView(this._data, index, viewData); + viewRef_.attachToViewContainerRef(this); return viewRef; } @@ -147,6 +153,7 @@ class ViewContainerRef_ implements ViewContainerRef { detach(index?: number): ViewRef { const view = this.get(index); Services.detachEmbeddedView(this._data, index); + (view as ViewRef_).detachFromContainer(); return view; } } @@ -155,11 +162,17 @@ export function createChangeDetectorRef(view: ViewData): ChangeDetectorRef { return new ViewRef_(view); } -class ViewRef_ implements EmbeddedViewRef { +export class ViewRef_ implements EmbeddedViewRef { /** @internal */ _view: ViewData; + private _viewContainerRef: ViewContainerRef; + private _appRef: ApplicationRef; - constructor(_view: ViewData) { this._view = _view; } + constructor(_view: ViewData) { + this._view = _view; + this._viewContainerRef = null; + this._appRef = null; + } get rootNodes(): any[] { return rootRenderNodes(this._view); } @@ -173,9 +186,40 @@ class ViewRef_ implements EmbeddedViewRef { checkNoChanges(): void { Services.checkNoChangesView(this._view); } reattach(): void { this._view.state |= ViewState.ChecksEnabled; } - onDestroy(callback: Function) { this._view.disposables.push(callback); } + onDestroy(callback: Function) { + if (!this._view.disposables) { + this._view.disposables = []; + } + this._view.disposables.push(callback); + } - destroy() { Services.destroyView(this._view); } + destroy() { + if (this._appRef) { + this._appRef.detachView(this); + } else if (this._viewContainerRef) { + this._viewContainerRef.detach(this._viewContainerRef.indexOf(this)); + } + Services.destroyView(this._view); + } + + detachFromContainer() { + this._appRef = null; + this._viewContainerRef = null; + } + + attachToAppRef(appRef: ApplicationRef) { + if (this._viewContainerRef) { + throw new Error('This view is already attached to a ViewContainer!'); + } + this._appRef = appRef; + } + + attachToViewContainerRef(vcRef: ViewContainerRef) { + if (this._appRef) { + throw new Error('This view is already attached directly to the ApplicationRef!'); + } + this._viewContainerRef = vcRef; + } } export function createTemplateRef(view: ViewData, def: NodeDef): TemplateRef { @@ -207,3 +251,137 @@ class Injector_ implements Injector { {flags: DepFlags.None, token, tokenKey: tokenKey(token)}, notFoundValue); } } + +export function nodeValue(view: ViewData, index: number): any { + const def = view.def.nodes[index]; + switch (def.type) { + case NodeType.Element: + if (def.element.template) { + return createTemplateRef(view, def); + } else { + return asElementData(view, def.index).renderElement; + } + case NodeType.Text: + return asTextData(view, def.index).renderText; + case NodeType.Directive: + case NodeType.Pipe: + case NodeType.Provider: + return asProviderData(view, def.index).instance; + } + return undefined; +} + +export function createRendererV1(view: ViewData): RendererV1 { + return new RendererAdapter(view.renderer); +} + +class RendererAdapter implements RendererV1 { + constructor(private delegate: RendererV2) {} + selectRootElement(selectorOrNode: string|Element): Element { + return this.delegate.selectRootElement(selectorOrNode); + } + + createElement(parent: Element|DocumentFragment, namespaceAndName: string): Element { + const [ns, name] = splitNamespace(namespaceAndName); + const el = this.delegate.createElement(name, ns); + if (parent) { + this.delegate.appendChild(parent, el); + } + return el; + } + + createViewRoot(hostElement: Element): Element|DocumentFragment { return hostElement; } + + createTemplateAnchor(parentElement: Element|DocumentFragment): Comment { + const comment = this.delegate.createComment(''); + if (parentElement) { + this.delegate.appendChild(parentElement, comment); + } + return comment; + } + + createText(parentElement: Element|DocumentFragment, value: string): any { + const node = this.delegate.createText(value); + if (parentElement) { + this.delegate.appendChild(parentElement, node); + } + return node; + } + + projectNodes(parentElement: Element|DocumentFragment, nodes: Node[]) { + for (let i = 0; i < nodes.length; i++) { + this.delegate.appendChild(parentElement, nodes[i]); + } + } + + attachViewAfter(node: Node, viewRootNodes: Node[]) { + const parentElement = this.delegate.parentNode(node); + const nextSibling = this.delegate.nextSibling(node); + for (let i = 0; i < viewRootNodes.length; i++) { + this.delegate.insertBefore(parentElement, viewRootNodes[i], nextSibling); + } + } + + detachView(viewRootNodes: (Element|Text|Comment)[]) { + for (let i = 0; i < viewRootNodes.length; i++) { + const node = viewRootNodes[i]; + const parentElement = this.delegate.parentNode(node); + this.delegate.removeChild(parentElement, node); + } + } + + destroyView(hostElement: Element|DocumentFragment, viewAllNodes: Node[]) { + for (let i = 0; i < viewAllNodes.length; i++) { + this.delegate.destroyNode(viewAllNodes[i]); + } + } + + listen(renderElement: any, name: string, callback: Function): Function { + return this.delegate.listen(renderElement, name, callback); + } + + listenGlobal(target: string, name: string, callback: Function): Function { + return this.delegate.listen(target, name, callback); + } + + setElementProperty( + renderElement: Element|DocumentFragment, propertyName: string, propertyValue: any): void { + this.delegate.setProperty(renderElement, propertyName, propertyValue); + } + + setElementAttribute(renderElement: Element, namespaceAndName: string, attributeValue: string): + void { + const [ns, name] = splitNamespace(namespaceAndName); + if (attributeValue != null) { + this.delegate.setAttribute(renderElement, name, attributeValue, ns); + } else { + this.delegate.removeAttribute(renderElement, name, ns); + } + } + + setBindingDebugInfo(renderElement: Element, propertyName: string, propertyValue: string): void {} + + setElementClass(renderElement: Element, className: string, isAdd: boolean): void { + if (isAdd) { + this.delegate.addClass(renderElement, className); + } else { + this.delegate.removeClass(renderElement, className); + } + } + + setElementStyle(renderElement: HTMLElement, styleName: string, styleValue: string): void { + if (styleValue != null) { + this.delegate.setStyle(renderElement, styleName, styleValue, false, false); + } else { + this.delegate.removeStyle(renderElement, styleName, false); + } + } + + invokeElementMethod(renderElement: Element, methodName: string, args: any[]): void { + (renderElement as any)[methodName].apply(renderElement, args); + } + + setText(renderNode: Text, text: string): void { this.delegate.setValue(renderNode, text); } + + animate(): NoOpAnimationPlayer { return new NoOpAnimationPlayer(); } +} \ No newline at end of file diff --git a/modules/@angular/core/src/view/services.ts b/modules/@angular/core/src/view/services.ts index 2353bc8485..f692dc1009 100644 --- a/modules/@angular/core/src/view/services.ts +++ b/modules/@angular/core/src/view/services.ts @@ -168,7 +168,9 @@ function debugUpdateDirectives(check: NodeCheckFn, view: ViewData) { function debugCheckDirectivesFn( view: ViewData, nodeIndex: number, argStyle: ArgumentType, ...values: any[]) { const result = debugCheckFn(check, view, nodeIndex, argStyle, values); - debugSetCurrentNode(view, nextDirectiveWithBinding(view, nodeIndex)); + if (view.def.nodes[nodeIndex].type === NodeType.Directive) { + debugSetCurrentNode(view, nextDirectiveWithBinding(view, nodeIndex)); + } return result; }; } @@ -183,7 +185,10 @@ function debugUpdateRenderer(check: NodeCheckFn, view: ViewData) { function debugCheckRenderNodeFn( view: ViewData, nodeIndex: number, argStyle: ArgumentType, ...values: any[]) { const result = debugCheckFn(check, view, nodeIndex, argStyle, values); - debugSetCurrentNode(view, nextRenderNodeWithBinding(view, nodeIndex)); + const nodeDef = view.def.nodes[nodeIndex]; + if (nodeDef.type === NodeType.Element || nodeDef.type === NodeType.Text) { + debugSetCurrentNode(view, nextRenderNodeWithBinding(view, nodeIndex)); + } return result; } } @@ -271,7 +276,7 @@ class DebugContext_ implements DebugContext { private compProviderDef: NodeDef; constructor(public view: ViewData, public nodeIndex: number) { if (nodeIndex == null) { - this.nodeIndex = 0; + this.nodeIndex = nodeIndex = 0; } this.nodeDef = view.def.nodes[nodeIndex]; let elDef = this.nodeDef; diff --git a/modules/@angular/core/src/view/text.ts b/modules/@angular/core/src/view/text.ts index 0d682d4a17..84b3ac31fa 100644 --- a/modules/@angular/core/src/view/text.ts +++ b/modules/@angular/core/src/view/text.ts @@ -20,6 +20,7 @@ export function textDef(ngContentIndex: number, constants: string[]): NodeDef { bindings[i - 1] = { type: BindingType.TextInterpolation, name: undefined, + ns: undefined, nonMinifiedName: undefined, securityContext: undefined, suffix: constants[i] diff --git a/modules/@angular/core/src/view/types.ts b/modules/@angular/core/src/view/types.ts index 7bfda9c1cd..17b82b1ce8 100644 --- a/modules/@angular/core/src/view/types.ts +++ b/modules/@angular/core/src/view/types.ts @@ -161,6 +161,7 @@ export enum NodeFlags { export interface BindingDef { type: BindingType; + ns: string; name: string; nonMinifiedName: string; securityContext: SecurityContext; @@ -187,7 +188,9 @@ export enum QueryValueType { export interface ElementDef { name: string; - attrs: {[name: string]: string}; + ns: string; + /** ns, name, value */ + attrs: [string, string, string][]; outputs: ElementOutputDef[]; template: ViewDefinition; component: NodeDef; diff --git a/modules/@angular/core/src/view/util.ts b/modules/@angular/core/src/view/util.ts index 005302c720..8da0452fc0 100644 --- a/modules/@angular/core/src/view/util.ts +++ b/modules/@angular/core/src/view/util.ts @@ -131,21 +131,6 @@ 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.Directive: - case NodeType.Pipe: - case NodeType.Provider: - return asProviderData(view, def.index).instance; - } - return undefined; -} - export function elementEventFullName(target: string, name: string): string { return target ? `${target}:${name}` : name; } @@ -184,13 +169,20 @@ export function splitMatchedQueriesDsl(matchedQueriesDsl: [string | number, Quer } export function getParentRenderElement(view: ViewData, renderHost: any, def: NodeDef): any { - let parentEl: any; - if (!def.parent) { - parentEl = renderHost; - } else if (def.renderParent) { - parentEl = asElementData(view, def.renderParent.index).renderElement; + let renderParent = def.renderParent; + if (renderParent) { + const parent = def.parent; + if (parent && (parent.type !== NodeType.Element || !parent.element.component || + (parent.element.component.provider.componentRenderType && + parent.element.component.provider.componentRenderType.encapsulation === + ViewEncapsulation.Native))) { + // only children of non components, or children of components with native encapsulation should + // be attached. + return asElementData(view, def.renderParent.index).renderElement; + } + } else { + return renderHost; } - return parentEl; } const VIEW_DEFINITION_CACHE = new WeakMap(); @@ -332,3 +324,13 @@ function execRenderNodeAction( break; } } + +const NS_PREFIX_RE = /^:([^:]+):(.+)$/; + +export function splitNamespace(name: string): string[] { + if (name[0] === ':') { + const match = name.match(NS_PREFIX_RE); + return [match[1], match[2]]; + } + return ['', name]; +} diff --git a/modules/@angular/core/src/view/view.ts b/modules/@angular/core/src/view/view.ts index a1d837ffba..fa3a4bab8c 100644 --- a/modules/@angular/core/src/view/view.ts +++ b/modules/@angular/core/src/view/view.ts @@ -55,18 +55,12 @@ export function viewDef( node.reverseChildIndex = calculateReverseChildIndex(currentParent, i, node.childCount, nodes.length); + // renderParent needs to account for ng-container! let currentRenderParent: NodeDef; - if (currentParent && - (currentParent.type !== NodeType.Element || !currentParent.element.component || - (currentParent.element.component.provider.componentRenderType && - currentParent.element.component.provider.componentRenderType.encapsulation === - ViewEncapsulation.Native))) { - // children of components that don't use native encapsulation should never be attached! - if (currentParent && currentParent.type === NodeType.Element && !currentParent.element.name) { - currentRenderParent = currentParent.renderParent; - } else { - currentRenderParent = currentParent; - } + if (currentParent && currentParent.type === NodeType.Element && !currentParent.element.name) { + currentRenderParent = currentParent.renderParent; + } else { + currentRenderParent = currentParent; } node.renderParent = currentRenderParent; @@ -98,7 +92,7 @@ export function viewDef( viewBindingCount += node.bindings.length; viewDisposableCount += node.disposableCount; - if (!currentParent) { + if (!currentRenderParent) { lastRootNode = node; } if (node.type === NodeType.Provider || node.type === NodeType.Directive) { @@ -204,10 +198,13 @@ function validateNode(parent: NodeDef, node: NodeDef, nodeCount: number) { } } if (node.query) { - const parentType = parent ? parent.type : null; - if (parentType !== NodeType.Directive) { + if (node.flags & NodeFlags.HasContentQuery && (!parent || parent.type !== NodeType.Directive)) { throw new Error( - `Illegal State: Query nodes need to be children of directives, at index ${node.index}!`); + `Illegal State: Content Query nodes need to be children of directives, at index ${node.index}!`); + } + if (node.flags & NodeFlags.HasViewQuery && parent) { + throw new Error( + `Illegal State: View Query nodes have to be top level nodes, at index ${node.index}!`); } } if (node.childCount) { @@ -593,8 +590,6 @@ function execQueriesAction( for (let i = 0; i < nodeCount; i++) { const nodeDef = view.def.nodes[i]; if ((nodeDef.flags & queryFlags) && (nodeDef.flags & staticDynamicQueryFlag)) { - const elDef = nodeDef.parent.parent; - Services.setCurrentNode(view, nodeDef.index); switch (action) { case QueryAction.CheckAndUpdate: diff --git a/modules/@angular/core/test/application_ref_spec.ts b/modules/@angular/core/test/application_ref_spec.ts index 499c1beeee..c77a9f4122 100644 --- a/modules/@angular/core/test/application_ref_spec.ts +++ b/modules/@angular/core/test/application_ref_spec.ts @@ -10,6 +10,7 @@ import {APP_BOOTSTRAP_LISTENER, APP_INITIALIZER, CompilerFactory, Component, NgM import {ApplicationRef, ApplicationRef_} from '@angular/core/src/application_ref'; import {ErrorHandler} from '@angular/core/src/error_handler'; import {ComponentRef} from '@angular/core/src/linker/component_factory'; +import {TestComponentRenderer} from '@angular/core/testing'; import {BrowserModule} from '@angular/platform-browser'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {DOCUMENT} from '@angular/platform-browser/src/dom/dom_tokens'; @@ -19,21 +20,26 @@ import {ServerModule} from '@angular/platform-server'; import {ComponentFixture, ComponentFixtureNoNgZone, TestBed, async, inject, withModule} from '../testing'; -@Component({selector: 'comp', template: 'hello'}) +@Component({selector: 'bootstrap-app', template: 'hello'}) class SomeComponent { } export function main() { describe('bootstrap', () => { let mockConsole: MockConsole; - let fakeDoc: Document; - beforeEach(() => { - fakeDoc = getDOM().createHtmlDocument(); - const el = getDOM().createElement('comp', fakeDoc); - getDOM().appendChild(fakeDoc.body, el); - mockConsole = new MockConsole(); - }); + beforeEach(() => { mockConsole = new MockConsole(); }); + + function createRootEl() { + const doc = TestBed.get(DOCUMENT); + const rootEl = getDOM().firstChild( + getDOM().content(getDOM().createTemplate(``))); + const oldRoots = getDOM().querySelectorAll(doc, 'bootstrap-app'); + for (let i = 0; i < oldRoots.length; i++) { + getDOM().remove(oldRoots[i]); + } + getDOM().appendChild(doc.body, rootEl); + } type CreateModuleOptions = {providers?: any[], ngDoBootstrap?: any, bootstrap?: any[]}; @@ -52,10 +58,7 @@ export function main() { const platformModule = getDOM().supportsDOMEvents() ? BrowserModule : ServerModule; @NgModule({ - providers: [ - {provide: ErrorHandler, useValue: errorHandler}, {provide: DOCUMENT, useValue: fakeDoc}, - options.providers || [] - ], + providers: [{provide: ErrorHandler, useValue: errorHandler}, options.providers || []], imports: [platformModule], declarations: [SomeComponent], entryComponents: [SomeComponent], @@ -74,7 +77,8 @@ export function main() { it('should throw when reentering tick', inject([ApplicationRef], (ref: ApplicationRef_) => { const view = jasmine.createSpyObj('view', ['detach', 'attachToAppRef']); - const viewRef = jasmine.createSpyObj('viewRef', ['detectChanges']); + const viewRef = jasmine.createSpyObj( + 'viewRef', ['detectChanges', 'detachFromContainer', 'attachToAppRef']); viewRef.internalView = view; view.ref = viewRef; try { @@ -101,16 +105,13 @@ export function main() { it('should be called when a component is bootstrapped', inject([ApplicationRef], (ref: ApplicationRef_) => { + createRootEl(); const compRef = ref.bootstrap(SomeComponent); expect(capturedCompRefs).toEqual([compRef]); })); }); describe('bootstrap', () => { - beforeEach( - () => { - - }); it('should throw if an APP_INITIIALIZER is not yet resolved', withModule( { @@ -119,6 +120,7 @@ export function main() { ] }, inject([ApplicationRef], (ref: ApplicationRef_) => { + createRootEl(); expect(() => ref.bootstrap(SomeComponent)) .toThrowError( 'Cannot bootstrap as there are still asynchronous initializers running. Bootstrap components in the `ngDoBootstrap` method of the root module.'); @@ -128,8 +130,10 @@ export function main() { describe('bootstrapModule', () => { let defaultPlatform: PlatformRef; - beforeEach( - inject([PlatformRef], (_platform: PlatformRef) => { defaultPlatform = _platform; })); + beforeEach(inject([PlatformRef], (_platform: PlatformRef) => { + createRootEl(); + defaultPlatform = _platform; + })); it('should wait for asynchronous app initializers', async(() => { let resolve: (result: any) => void; @@ -221,8 +225,10 @@ export function main() { describe('bootstrapModuleFactory', () => { let defaultPlatform: PlatformRef; - beforeEach( - inject([PlatformRef], (_platform: PlatformRef) => { defaultPlatform = _platform; })); + beforeEach(inject([PlatformRef], (_platform: PlatformRef) => { + createRootEl(); + defaultPlatform = _platform; + })); it('should wait for asynchronous app initializers', async(() => { let resolve: (result: any) => void; const promise: Promise = new Promise((res) => { resolve = res; }); @@ -346,7 +352,7 @@ export function main() { it('should not allow to attach a view to both, a view container and the ApplicationRef', () => { const comp = TestBed.createComponent(MyComp); - const hostView = comp.componentRef.hostView; + let hostView = comp.componentRef.hostView; const containerComp = TestBed.createComponent(ContainerComp); containerComp.detectChanges(); const vc = containerComp.componentInstance.vc; @@ -355,7 +361,7 @@ export function main() { vc.insert(hostView); expect(() => appRef.attachView(hostView)) .toThrowError('This view is already attached to a ViewContainer!'); - vc.detach(0); + hostView = vc.detach(0); appRef.attachView(hostView); expect(() => vc.insert(hostView)) diff --git a/modules/@angular/core/test/linker/ng_container_integration_spec.ts b/modules/@angular/core/test/linker/ng_container_integration_spec.ts index 70301d6323..a79c6939ca 100644 --- a/modules/@angular/core/test/linker/ng_container_integration_spec.ts +++ b/modules/@angular/core/test/linker/ng_container_integration_spec.ts @@ -9,7 +9,6 @@ import {AfterContentInit, AfterViewInit, Component, ContentChildren, Directive, Input, QueryList, ViewChildren} from '@angular/core'; import {TestBed} from '@angular/core/testing'; -import {beforeEach, describe, it} from '@angular/core/testing/testing_internal'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {expect} from '@angular/platform-browser/testing/matchers'; diff --git a/modules/@angular/core/test/linker/security_integration_spec.ts b/modules/@angular/core/test/linker/security_integration_spec.ts index 6b1d6bf5c8..42ae64b5d8 100644 --- a/modules/@angular/core/test/linker/security_integration_spec.ts +++ b/modules/@angular/core/test/linker/security_integration_spec.ts @@ -8,7 +8,6 @@ import {Component, Directive, HostBinding, Input, NO_ERRORS_SCHEMA} from '@angular/core'; import {ComponentFixture, TestBed, getTestBed} from '@angular/core/testing'; -import {afterEach, beforeEach, describe, expect, iit, it} from '@angular/core/testing/testing_internal'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {DomSanitizer} from '@angular/platform-browser/src/security/dom_sanitization_service'; diff --git a/modules/@angular/core/test/view/component_view_spec.ts b/modules/@angular/core/test/view/component_view_spec.ts index 1eb8209127..c0f3b81ffd 100644 --- a/modules/@angular/core/test/view/component_view_spec.ts +++ b/modules/@angular/core/test/view/component_view_spec.ts @@ -82,7 +82,7 @@ export function main() { it('should set attributes on the root node', () => { const view = createRootView( compViewDef([ - elementDef(NodeFlags.None, null, null, 0, 'div', {'a': 'b'}), + elementDef(NodeFlags.None, null, null, 0, 'div', [['a', 'b']]), ]), {}, [], rootNode); expect(rootNode.getAttribute('a')).toBe('b'); @@ -92,7 +92,7 @@ export function main() { rootNode.appendChild(document.createElement('div')); const view = createRootView( compViewDef([ - elementDef(NodeFlags.None, null, null, 0, '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 66cab0cdf6..7ae37b768e 100644 --- a/modules/@angular/core/test/view/element_spec.ts +++ b/modules/@angular/core/test/view/element_spec.ts @@ -57,7 +57,7 @@ export function main() { it('should set fixed attributes', () => { const rootNodes = createAndGetRootNodes(compViewDef([ - elementDef(NodeFlags.None, null, null, 0, 'div', {'title': 'a'}), + elementDef(NodeFlags.None, null, null, 0, 'div', [['title', 'a']]), ])).rootNodes; expect(rootNodes.length).toBe(1); expect(getDOM().getAttribute(rootNodes[0], 'title')).toBe('a'); diff --git a/modules/@angular/core/test/view/embedded_view_spec.ts b/modules/@angular/core/test/view/embedded_view_spec.ts index df802cb038..d245876b4c 100644 --- a/modules/@angular/core/test/view/embedded_view_spec.ts +++ b/modules/@angular/core/test/view/embedded_view_spec.ts @@ -55,10 +55,10 @@ export function main() { const {view: parentView, rootNodes} = createAndGetRootNodes(compViewDef([ elementDef(NodeFlags.None, null, null, 2, 'div'), anchorDef(NodeFlags.HasEmbeddedViews, null, null, 0, embeddedViewDef([ - elementDef(NodeFlags.None, null, null, 0, 'span', {'name': 'child0'}) + elementDef(NodeFlags.None, null, null, 0, 'span', [['name', 'child0']]) ])), anchorDef(NodeFlags.None, null, null, 0, embeddedViewDef([ - elementDef(NodeFlags.None, null, null, 0, 'span', {'name': 'child1'}) + elementDef(NodeFlags.None, null, null, 0, 'span', [['name', 'child1']]) ])) ])); const viewContainerData = asElementData(parentView, 1); @@ -85,10 +85,10 @@ export function main() { const {view: parentView, rootNodes} = createAndGetRootNodes(compViewDef([ elementDef(NodeFlags.None, null, null, 2, 'div'), anchorDef(NodeFlags.HasEmbeddedViews, null, null, 0, embeddedViewDef([ - elementDef(NodeFlags.None, null, null, 0, 'span', {'name': 'child0'}) + elementDef(NodeFlags.None, null, null, 0, 'span', [['name', 'child0']]) ])), anchorDef(NodeFlags.None, null, null, 0, embeddedViewDef([ - elementDef(NodeFlags.None, null, null, 0, 'span', {'name': 'child1'}) + elementDef(NodeFlags.None, null, null, 0, 'span', [['name', 'child1']]) ])) ])); const viewContainerData = asElementData(parentView, 1); @@ -112,9 +112,9 @@ export function main() { it('should include embedded views in root nodes', () => { const {view: parentView} = createAndGetRootNodes(compViewDef([ anchorDef(NodeFlags.HasEmbeddedViews, null, null, 0, embeddedViewDef([ - elementDef(NodeFlags.None, null, null, 0, 'span', {'name': 'child0'}) + elementDef(NodeFlags.None, null, null, 0, 'span', [['name', 'child0']]) ])), - elementDef(NodeFlags.None, null, null, 0, 'span', {'name': 'after'}) + elementDef(NodeFlags.None, null, null, 0, 'span', [['name', 'after']]) ])); const childView0 = Services.createEmbeddedView(parentView, parentView.def.nodes[0]); diff --git a/modules/@angular/core/test/view/query_spec.ts b/modules/@angular/core/test/view/query_spec.ts index 2eeb37cdcd..350ca0dd9a 100644 --- a/modules/@angular/core/test/view/query_spec.ts +++ b/modules/@angular/core/test/view/query_spec.ts @@ -50,12 +50,16 @@ export function main() { ]; } - function viewQueryProviders(compView: ViewDefinition) { + function viewQueryProviders(nodes: NodeDef[]) { return [ - directiveDef(NodeFlags.None, null, 1, QueryService, [], null, null, () => compView), - queryDef( - NodeFlags.HasViewQuery | NodeFlags.HasDynamicQuery, someQueryId, - {'a': QueryBindingType.All}) + directiveDef( + NodeFlags.None, null, 0, QueryService, [], null, null, + () => compViewDef([ + queryDef( + NodeFlags.HasViewQuery | NodeFlags.HasDynamicQuery, someQueryId, + {'a': QueryBindingType.All}), + ...nodes + ])), ]; } @@ -106,11 +110,11 @@ export function main() { describe('view queries', () => { it('should query providers in the view', () => { const {view} = createAndGetRootNodes(compViewDef([ - elementDef(NodeFlags.None, null, null, 2, 'div'), - ...viewQueryProviders(compViewDef([ + elementDef(NodeFlags.None, null, null, 1, 'div'), + ...viewQueryProviders([ elementDef(NodeFlags.None, null, null, 1, 'span'), aServiceProvider(), - ])), + ]), ])); Services.checkAndUpdateView(view); @@ -118,15 +122,15 @@ export function main() { const comp: QueryService = asProviderData(view, 1).instance; const compView = asProviderData(view, 1).componentView; expect(comp.a.length).toBe(1); - expect(comp.a.first).toBe(asProviderData(compView, 1).instance); + expect(comp.a.first).toBe(asProviderData(compView, 2).instance); }); it('should not query providers on the host element', () => { const {view} = createAndGetRootNodes(compViewDef([ - elementDef(NodeFlags.None, null, null, 3, 'div'), - ...viewQueryProviders(compViewDef([ + elementDef(NodeFlags.None, null, null, 2, 'div'), + ...viewQueryProviders([ elementDef(NodeFlags.None, null, null, 0, 'span'), - ])), + ]), aServiceProvider(), ])); @@ -221,13 +225,13 @@ export function main() { it('should update view queries if embedded views are added or removed', () => { const {view} = createAndGetRootNodes(compViewDef([ - elementDef(NodeFlags.None, null, null, 2, 'div'), - ...viewQueryProviders(compViewDef([ + elementDef(NodeFlags.None, null, null, 1, 'div'), + ...viewQueryProviders([ anchorDef(NodeFlags.HasEmbeddedViews, null, null, 0, embeddedViewDef([ elementDef(NodeFlags.None, null, null, 1, 'div'), aServiceProvider(), ])), - ])), + ]), ])); Services.checkAndUpdateView(view); @@ -236,13 +240,13 @@ export function main() { expect(comp.a.length).toBe(0); const compView = asProviderData(view, 1).componentView; - const childView = Services.createEmbeddedView(compView, compView.def.nodes[0]); - attachEmbeddedView(asElementData(compView, 0), 0, childView); + const childView = Services.createEmbeddedView(compView, compView.def.nodes[1]); + attachEmbeddedView(asElementData(compView, 1), 0, childView); Services.checkAndUpdateView(view); expect(comp.a.length).toBe(1); - detachEmbeddedView(asElementData(compView, 0), 0); + detachEmbeddedView(asElementData(compView, 1), 0); Services.checkAndUpdateView(view); expect(comp.a.length).toBe(0); diff --git a/modules/@angular/language-service/src/language_service.ts b/modules/@angular/language-service/src/language_service.ts index 226853ae03..fd6a740acc 100644 --- a/modules/@angular/language-service/src/language_service.ts +++ b/modules/@angular/language-service/src/language_service.ts @@ -8,6 +8,7 @@ import {NgAnalyzedModules} from '@angular/compiler/src/aot/compiler'; import {CompileNgModuleMetadata} from '@angular/compiler/src/compile_metadata'; +import {CompilerConfig} from '@angular/compiler/src/config'; import {Lexer} from '@angular/compiler/src/expression_parser/lexer'; import {Parser} from '@angular/compiler/src/expression_parser/parser'; import {I18NHtmlParser} from '@angular/compiler/src/i18n/i18n_html_parser'; @@ -23,6 +24,7 @@ import {getDeclarationDiagnostics, getTemplateDiagnostics} from './diagnostics'; import {getHover} from './hover'; import {Completion, CompletionKind, Completions, Declaration, Declarations, Definition, Diagnostic, DiagnosticKind, Diagnostics, Hover, LanguageService, LanguageServiceHost, Location, PipeInfo, Pipes, Signature, Span, Symbol, SymbolDeclaration, SymbolQuery, SymbolTable, TemplateSource, TemplateSources} from './types'; + /** * Create an instance of an Angular `LanguageService`. * @@ -114,8 +116,9 @@ class LanguageServiceImpl implements LanguageService { const rawHtmlParser = new HtmlParser(); const htmlParser = new I18NHtmlParser(rawHtmlParser); const expressionParser = new Parser(new Lexer()); + const config = new CompilerConfig(); const parser = new TemplateParser( - expressionParser, new DomElementSchemaRegistry(), htmlParser, null, []); + config, expressionParser, new DomElementSchemaRegistry(), htmlParser, null, []); const htmlResult = htmlParser.parse(template.source, ''); const analyzedModules = this.host.getAnalyzedModules(); let errors: Diagnostic[] = undefined; diff --git a/modules/@angular/language-service/src/typescript_host.ts b/modules/@angular/language-service/src/typescript_host.ts index 543e5d64e3..3fbb68f260 100644 --- a/modules/@angular/language-service/src/typescript_host.ts +++ b/modules/@angular/language-service/src/typescript_host.ts @@ -131,7 +131,7 @@ export class TypeScriptServiceHost implements LanguageServiceHost { new DirectiveNormalizer(resourceLoader, urlResolver, htmlParser, config); result = this._resolver = new CompileMetadataResolver( - moduleResolver, directiveResolver, pipeResolver, new SummaryResolver(), + config, moduleResolver, directiveResolver, pipeResolver, new SummaryResolver(), elementSchemaRegistry, directiveNormalizer, this._staticSymbolCache, this.reflector, (error, type) => this.collectError(error, type && type.filePath)); } diff --git a/modules/@angular/platform-browser/src/browser/tools/common_tools.ts b/modules/@angular/platform-browser/src/browser/tools/common_tools.ts index 24bffe74d4..d35147adcf 100644 --- a/modules/@angular/platform-browser/src/browser/tools/common_tools.ts +++ b/modules/@angular/platform-browser/src/browser/tools/common_tools.ts @@ -17,16 +17,6 @@ export class ChangeDetectionPerfRecord { constructor(public msPerTick: number, public numTicks: number) {} } -/** - * Entry point for all Angular debug tools. This object corresponds to the `ng` - * global variable accessible in the dev console. - */ -export class AngularTools { - profiler: AngularProfiler; - - constructor(ref: ComponentRef) { this.profiler = new AngularProfiler(ref); } -} - /** * Entry point for all Angular profiling-related debug tools. This object * corresponds to the `ng.profiler` in the dev console. diff --git a/modules/@angular/platform-browser/src/browser/tools/tools.ts b/modules/@angular/platform-browser/src/browser/tools/tools.ts index 2cf1c176dc..dd771943a4 100644 --- a/modules/@angular/platform-browser/src/browser/tools/tools.ts +++ b/modules/@angular/platform-browser/src/browser/tools/tools.ts @@ -7,11 +7,11 @@ */ import {ComponentRef} from '@angular/core'; -import {global} from '../../facade/lang'; +import {getDOM} from '../../dom/dom_adapter'; -import {AngularTools} from './common_tools'; +import {AngularProfiler} from './common_tools'; -const context = global; +const PROFILER_GLOBAL_NAME = 'ng.profiler'; /** * Enabled Angular debug tools that are accessible via your browser's @@ -27,7 +27,7 @@ const context = global; * @experimental All debugging apis are currently experimental. */ export function enableDebugTools(ref: ComponentRef): ComponentRef { - (Object).assign(context.ng, new AngularTools(ref)); + getDOM().setGlobalVar(PROFILER_GLOBAL_NAME, new AngularProfiler(ref)); return ref; } @@ -37,7 +37,5 @@ export function enableDebugTools(ref: ComponentRef): ComponentRef { * @experimental All debugging apis are currently experimental. */ export function disableDebugTools(): void { - if (context.ng) { - delete context.ng.profiler; - } + getDOM().setGlobalVar(PROFILER_GLOBAL_NAME, null); } diff --git a/modules/@angular/platform-browser/src/dom/dom_renderer.ts b/modules/@angular/platform-browser/src/dom/dom_renderer.ts index 2c1705b106..9cc734c7fb 100644 --- a/modules/@angular/platform-browser/src/dom/dom_renderer.ts +++ b/modules/@angular/platform-browser/src/dom/dom_renderer.ts @@ -434,6 +434,9 @@ class DefaultDomRendererV2 implements RendererV2 { selectRootElement(selectorOrNode: string|any): any { let el: any = typeof selectorOrNode === 'string' ? document.querySelector(selectorOrNode) : selectorOrNode; + if (!el) { + throw new Error(`The selector "${selectorOrNode}" did not match any elements`); + } el.textContent = ''; return el; } @@ -464,13 +467,21 @@ class DefaultDomRendererV2 implements RendererV2 { setStyle(el: any, style: string, value: any, hasVendorPrefix: boolean, hasImportant: boolean): void { - el.style[style] = value; + if (hasVendorPrefix || hasImportant) { + el.style.setProperty(style, value, hasImportant ? 'important' : ''); + } else { + el.style[style] = value; + } } removeStyle(el: any, style: string, hasVendorPrefix: boolean): void { - // IE requires '' instead of null - // see https://github.com/angular/angular/issues/7916 - el.style[style] = ''; + if (hasVendorPrefix) { + el.style.removeProperty(style); + } else { + // IE requires '' instead of null + // see https://github.com/angular/angular/issues/7916 + el.style[style] = ''; + } } setProperty(el: any, name: string, value: any): void { el[name] = value; } diff --git a/modules/@angular/platform-browser/test/browser/bootstrap_spec.ts b/modules/@angular/platform-browser/test/browser/bootstrap_spec.ts index d27cd97480..5142951a33 100644 --- a/modules/@angular/platform-browser/test/browser/bootstrap_spec.ts +++ b/modules/@angular/platform-browser/test/browser/bootstrap_spec.ts @@ -11,7 +11,7 @@ import {ApplicationRef, destroyPlatform} from '@angular/core/src/application_ref import {Console} from '@angular/core/src/console'; import {ComponentRef} from '@angular/core/src/linker/component_factory'; import {Testability, TestabilityRegistry} from '@angular/core/src/testability/testability'; -import {AsyncTestCompleter, Log, afterEach, beforeEach, beforeEachProviders, describe, inject, it} from '@angular/core/testing/testing_internal'; +import {AsyncTestCompleter, Log, afterEach, beforeEach, beforeEachProviders, ddescribe, describe, iit, inject, it} from '@angular/core/testing/testing_internal'; import {BrowserModule} from '@angular/platform-browser'; import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; @@ -20,6 +20,10 @@ import {expect} from '@angular/platform-browser/testing/matchers'; import {stringify} from '../../src/facade/lang'; +@Component({selector: 'non-existent', template: ''}) +class NonExistentComp { +} + @Component({selector: 'hello-app', template: '{{greeting}} world!'}) class HelloRootCmp { greeting: string; @@ -124,29 +128,32 @@ function bootstrap( } export function main() { - let fakeDoc: any /** TODO #9100 */, el: any /** TODO #9100 */, el2: any /** TODO #9100 */, - testProviders: Provider[], lightDom: any /** TODO #9100 */; + let el: any /** TODO #9100 */, el2: any /** TODO #9100 */, testProviders: Provider[], + lightDom: any /** TODO #9100 */; describe('bootstrap factory method', () => { let compilerConsole: DummyConsole; beforeEachProviders(() => { return [Log]; }); - beforeEach(() => { + beforeEach(inject([DOCUMENT], (doc: any) => { destroyPlatform(); + compilerConsole = new DummyConsole(); + testProviders = [{provide: Console, useValue: compilerConsole}]; - fakeDoc = getDOM().createHtmlDocument(); - el = getDOM().createElement('hello-app', fakeDoc); - el2 = getDOM().createElement('hello-app-2', fakeDoc); - lightDom = getDOM().createElement('light-dom-el', fakeDoc); - getDOM().appendChild(fakeDoc.body, el); - getDOM().appendChild(fakeDoc.body, el2); + const oldRoots = getDOM().querySelectorAll(doc, 'hello-app,hello-app-2,light-dom-el'); + for (let i = 0; i < oldRoots.length; i++) { + getDOM().remove(oldRoots[i]); + } + + el = getDOM().createElement('hello-app', doc); + el2 = getDOM().createElement('hello-app-2', doc); + lightDom = getDOM().createElement('light-dom-el', doc); + getDOM().appendChild(doc.body, el); + getDOM().appendChild(doc.body, el2); getDOM().appendChild(el, lightDom); getDOM().setText(lightDom, 'loading'); - compilerConsole = new DummyConsole(); - testProviders = - [{provide: DOCUMENT, useValue: fakeDoc}, {provide: Console, useValue: compilerConsole}]; - }); + })); afterEach(destroyPlatform); @@ -167,10 +174,11 @@ export function main() { const logger = new MockConsole(); const errorHandler = new ErrorHandler(false); errorHandler._console = logger as any; - bootstrap(HelloRootCmp, [ + bootstrap(NonExistentComp, [ {provide: ErrorHandler, useValue: errorHandler} ]).then(null, (reason) => { - expect(reason.message).toContain('The selector "hello-app" did not match any elements'); + expect(reason.message) + .toContain('The selector "non-existent" did not match any elements'); async.done(); return null; }); @@ -184,10 +192,10 @@ export function main() { errorHandler._console = logger as any; const refPromise = - bootstrap(HelloRootCmp, [{provide: ErrorHandler, useValue: errorHandler}]); + bootstrap(NonExistentComp, [{provide: ErrorHandler, useValue: errorHandler}]); refPromise.then(null, (reason: any) => { expect(reason.message) - .toContain('The selector "hello-app" did not match any elements'); + .toContain('The selector "non-existent" did not match any elements'); async.done(); }); })); @@ -199,10 +207,10 @@ export function main() { errorHandler._console = logger as any; const refPromise = - bootstrap(HelloRootCmp, [{provide: ErrorHandler, useValue: errorHandler}]); + bootstrap(NonExistentComp, [{provide: ErrorHandler, useValue: errorHandler}]); refPromise.then(null, (reason) => { expect(logger.res.join('')) - .toContain('The selector "hello-app" did not match any elements'); + .toContain('The selector "non-existent" did not match any elements'); async.done(); return null; }); diff --git a/modules/@angular/platform-browser/test/browser/tools/tools_spec.ts b/modules/@angular/platform-browser/test/browser/tools/tools_spec.ts index 5aa570822c..33676db30c 100644 --- a/modules/@angular/platform-browser/test/browser/tools/tools_spec.ts +++ b/modules/@angular/platform-browser/test/browser/tools/tools_spec.ts @@ -6,7 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ -import {afterEach, beforeEach, describe, it} from '@angular/core/testing/testing_internal'; import {disableDebugTools, enableDebugTools} from '@angular/platform-browser'; import {SpyComponentRef, callNgProfilerTimeChangeDetection} from './spies';