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 ca66573669..d4805f767a 100644 --- a/modules/@angular/compiler/src/view_compiler_next/view_compiler.ts +++ b/modules/@angular/compiler/src/view_compiler_next/view_compiler.ts @@ -282,7 +282,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter elName = null; } - let {flags, usedEvents, queryMatchesExpr, hostBindings, hostEvents} = + const {flags, usedEvents, queryMatchesExpr, hostBindings, hostEvents} = this._visitElementOrTemplate(nodeIndex, ast); let inputDefs: o.Expression[] = []; @@ -296,33 +296,43 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter // Note: inputDefs have to be in the same order as hostBindings: // - first the entries from the directives, then the ones from the element. ast.directives.forEach( - (dirAst, dirIndex) => inputDefs.push(...elementBindingDefs(dirAst.hostProperties))); - inputDefs.push(...elementBindingDefs(ast.inputs)); + (dirAst, dirIndex) => + inputDefs.push(...elementBindingDefs(dirAst.hostProperties, dirAst))); + inputDefs.push(...elementBindingDefs(ast.inputs, null)); - outputDefs = usedEvents.map(([target, eventName]) => { - return target ? o.literalArr([o.literal(target), o.literal(eventName)]) : - o.literal(eventName); - }); + outputDefs = usedEvents.map( + ([target, eventName]) => o.literalArr([o.literal(target), o.literal(eventName)])); } templateVisitAll(this, ast.children); const childCount = this.nodeDefs.length - nodeIndex - 1; + const compAst = ast.directives.find(dirAst => dirAst.directive.isComponent); + let compRendererType = o.NULL_EXPR; + let compView = o.NULL_EXPR; + if (compAst) { + compView = o.importExpr({reference: compAst.directive.componentViewType}); + compRendererType = o.importExpr({reference: compAst.directive.rendererType}); + } + // elementDef( - // flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number, - // childCount: number, name: string, fixedAttrs: {[name: string]: string} = {}, + // flags: NodeFlags, matchedQueriesDsl: [string | number, QueryValueType][], + // ngContentIndex: number, childCount: number, namespaceAndName: string, + // fixedAttrs: [string, string][] = [], // bindings?: // ([BindingType.ElementClass, string] | [BindingType.ElementStyle, string, string] | - // [BindingType.ElementAttribute | BindingType.ElementProperty, string, - // SecurityContext])[], - // outputs?: (string | [string, string])[], eventHandlerFn: ElementHandleEventFn): NodeDef; + // [BindingType.ElementAttribute | BindingType.ElementProperty | + // BindingType.DirectiveHostProperty, string, SecurityContext])[], + // outputs?: ([OutputType.ElementOutput | OutputType.DirectiveHostOutput, string, string])[], + // handleEvent?: ElementHandleEventFn, + // componentView?: () => ViewDefinition, componentRendererType?: RendererTypeV2): NodeDef; const nodeDef = () => o.importExpr(createIdentifier(Identifiers.elementDef)).callFn([ o.literal(flags), queryMatchesExpr, o.literal(ast.ngContentIndex), o.literal(childCount), 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, - this._createElementHandleEventFn(nodeIndex, hostEvents) + this._createElementHandleEventFn(nodeIndex, hostEvents), compView, compRendererType ]); this.nodeDefs[nodeIndex] = nodeDef; @@ -336,11 +346,11 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter references: ReferenceAst[], queryMatches: QueryMatch[] }): { - flags: number, + flags: NodeFlags, usedEvents: [string, string][], queryMatchesExpr: o.Expression, hostBindings: {value: AST, context: o.Expression}[], - hostEvents: {context: o.Expression, eventAst: BoundEventAst}[], + hostEvents: {context: o.Expression, eventAst: BoundEventAst, dirAst: DirectiveAst}[], } { let flags = NodeFlags.None; if (ast.hasViewContainer) { @@ -348,17 +358,17 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter } const usedEvents = new Map(); ast.outputs.forEach((event) => { - const en = eventName(event); - usedEvents.set(elementEventFullName(event.target, en), [event.target, en]); + const {name, target} = elementEventNameAndTarget(event, null); + usedEvents.set(elementEventFullName(target, name), [target, name]); }); ast.directives.forEach((dirAst) => { dirAst.hostEvents.forEach((event) => { - const en = eventName(event); - usedEvents.set(elementEventFullName(event.target, en), [event.target, en]); + const {name, target} = elementEventNameAndTarget(event, dirAst); + usedEvents.set(elementEventFullName(target, name), [target, name]); }); }); const hostBindings: {value: AST, context: o.Expression}[] = []; - const hostEvents: {context: o.Expression, eventAst: BoundEventAst}[] = []; + const hostEvents: {context: o.Expression, eventAst: BoundEventAst, dirAst: DirectiveAst}[] = []; const componentFactoryResolverProvider = createComponentFactoryResolver(ast.directives); if (componentFactoryResolverProvider) { this._visitProvider(componentFactoryResolverProvider, ast.queryMatches); @@ -386,7 +396,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter let queryMatchExprs: o.Expression[] = []; ast.queryMatches.forEach((match) => { - let valueType: number; + let valueType: QueryValueType; if (tokenReference(match.value) === resolveIdentifier(Identifiers.ElementRef)) { valueType = QueryValueType.ElementRef; } else if (tokenReference(match.value) === resolveIdentifier(Identifiers.ViewContainerRef)) { @@ -399,7 +409,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter } }); ast.references.forEach((ref) => { - let valueType: number; + let valueType: QueryValueType; if (!ref.value) { valueType = QueryValueType.RenderElement; } else if (tokenReference(ref.value) === resolveIdentifier(Identifiers.TemplateRef)) { @@ -410,8 +420,9 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter queryMatchExprs.push(o.literalArr([o.literal(ref.name), o.literal(valueType)])); } }); - ast.outputs.forEach( - (outputAst) => { hostEvents.push({context: COMP_VAR, eventAst: outputAst}); }); + ast.outputs.forEach((outputAst) => { + hostEvents.push({context: COMP_VAR, eventAst: outputAst, dirAst: null}); + }); return { flags, @@ -423,19 +434,19 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter } private _visitDirective( - providerAst: ProviderAst, directiveAst: DirectiveAst, directiveIndex: number, + providerAst: ProviderAst, dirAst: DirectiveAst, directiveIndex: number, elementNodeIndex: number, refs: ReferenceAst[], queryMatches: QueryMatch[], usedEvents: Map, queryIds: StaticAndDynamicQueryIds): { hostBindings: {value: AST, context: o.Expression}[], - hostEvents: {context: o.Expression, eventAst: BoundEventAst}[] + hostEvents: {context: o.Expression, eventAst: BoundEventAst, dirAst: DirectiveAst}[] } { const nodeIndex = this.nodeDefs.length; // reserve the space in the nodeDefs array so we can add children this.nodeDefs.push(null); - directiveAst.directive.queries.forEach((query, queryIndex) => { + dirAst.directive.queries.forEach((query, queryIndex) => { let flags = NodeFlags.HasContentQuery; - const queryId = directiveAst.contentQueryStartId + queryIndex; + const queryId = dirAst.contentQueryStartId + queryIndex; if (queryIds.staticQueryIds.has(queryId)) { flags |= NodeFlags.HasStaticQuery; } else { @@ -454,7 +465,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter // I.e. we only allow queries as children of directives nodes. const childCount = this.nodeDefs.length - nodeIndex - 1; - const {flags, queryMatchExprs, providerExpr, providerType, depsExpr} = + let {flags, queryMatchExprs, providerExpr, providerType, depsExpr} = this._visitProviderOrDirective(providerAst, queryMatches); refs.forEach((ref) => { @@ -465,21 +476,18 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter } }); - let rendererType = o.NULL_EXPR; - let compView = o.NULL_EXPR; - if (directiveAst.directive.isComponent) { - compView = o.importExpr({reference: directiveAst.directive.componentViewType}); - rendererType = o.importExpr({reference: directiveAst.directive.rendererType}); + if (dirAst.directive.isComponent) { + flags |= NodeFlags.IsComponent; } - const inputDefs = directiveAst.inputs.map((inputAst, inputIndex) => { + const inputDefs = dirAst.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; + const dirMeta = dirAst.directive; Object.keys(dirMeta.outputs).forEach((propName) => { const eventName = dirMeta.outputs[propName]; if (usedEvents.has(eventName)) { @@ -487,24 +495,24 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter outputDefs.push(new o.LiteralMapEntry(propName, o.literal(eventName), false)); } }); - if (directiveAst.inputs.length || (flags & (NodeFlags.DoCheck | NodeFlags.OnInit)) > 0) { + if (dirAst.inputs.length || (flags & (NodeFlags.DoCheck | NodeFlags.OnInit)) > 0) { this._addUpdateExpressions( nodeIndex, - directiveAst.inputs.map((input) => { return {context: COMP_VAR, value: input.value}; }), + dirAst.inputs.map((input) => { return {context: COMP_VAR, value: input.value}; }), this.updateDirectivesExpressions); } const dirContextExpr = o.importExpr(createIdentifier(Identifiers.nodeValue)).callFn([ VIEW_VAR, o.literal(nodeIndex) ]); - const hostBindings = directiveAst.hostProperties.map((hostBindingAst) => { + const hostBindings = dirAst.hostProperties.map((hostBindingAst) => { return { value: (hostBindingAst.value).ast, context: dirContextExpr, }; }); - const hostEvents = directiveAst.hostEvents.map( - (hostEventAst) => { return {context: dirContextExpr, eventAst: hostEventAst}; }); + const hostEvents = dirAst.hostEvents.map( + (hostEventAst) => { return {context: dirContextExpr, eventAst: hostEventAst, dirAst}; }); // directiveDef( @@ -516,7 +524,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter 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, rendererType + outputDefs.length ? new o.LiteralMapExpr(outputDefs) : o.NULL_EXPR ]); this.nodeDefs[nodeIndex] = nodeDef; @@ -543,10 +551,10 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter } private _visitProviderOrDirective(providerAst: ProviderAst, queryMatches: QueryMatch[]): { - flags: number, + flags: NodeFlags, queryMatchExprs: o.Expression[], providerExpr: o.Expression, - providerType: number, + providerType: ProviderType, depsExpr: o.Expression } { let flags = NodeFlags.None; @@ -702,10 +710,11 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter } private _createElementHandleEventFn( - nodeIndex: number, handlers: {context: o.Expression, eventAst: BoundEventAst}[]) { + nodeIndex: number, + handlers: {context: o.Expression, eventAst: BoundEventAst, dirAst: DirectiveAst}[]) { const handleEventStmts: o.Statement[] = []; let handleEventBindingCount = 0; - handlers.forEach(({context, eventAst}) => { + handlers.forEach(({context, eventAst, dirAst}) => { const bindingId = `${handleEventBindingCount++}`; const nameResolver = context === COMP_VAR ? this : null; const expression = @@ -716,7 +725,8 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter if (allowDefault) { trueStmts.push(ALLOW_DEFAULT_VAR.set(allowDefault.and(ALLOW_DEFAULT_VAR)).toStmt()); } - const fullEventName = elementEventFullName(eventAst.target, eventName(eventAst)); + const {target: eventTarget, name: eventName} = elementEventNameAndTarget(eventAst, dirAst); + const fullEventName = elementEventFullName(eventTarget, eventName); handleEventStmts.push( new o.IfStmt(o.literal(fullEventName).identical(EVENT_NAME_VAR), trueStmts)); }); @@ -751,13 +761,13 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter } function providerDef(providerAst: ProviderAst): - {providerExpr: o.Expression, providerType: number, depsExpr: o.Expression} { + {providerExpr: o.Expression, providerType: ProviderType, 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} { + {providerExpr: o.Expression, providerType: ProviderType, depsExpr: o.Expression} { const allDepDefs: o.Expression[] = []; const allParams: o.FnParam[] = []; const exprs = providers.map((provider, providerIndex) => { @@ -791,9 +801,9 @@ function multiProviderDef(providers: CompileProviderMetadata[]): } function singleProviderDef(providerMeta: CompileProviderMetadata): - {providerExpr: o.Expression, providerType: number, depsExpr: o.Expression} { + {providerExpr: o.Expression, providerType: ProviderType, depsExpr: o.Expression} { let providerExpr: o.Expression; - let providerType: number; + let providerType: ProviderType; let deps: CompileDiDependencyMetadata[]; if (providerMeta.useClass) { providerExpr = o.importExpr(providerMeta.useClass); @@ -852,7 +862,7 @@ function needsAdditionalRootNode(ast: TemplateAst): boolean { return ast instanceof NgContentAst; } -function lifecycleHookToNodeFlag(lifecycleHook: LifecycleHooks): number { +function lifecycleHookToNodeFlag(lifecycleHook: LifecycleHooks): NodeFlags { let nodeFlag = NodeFlags.None; switch (lifecycleHook) { case LifecycleHooks.AfterContentChecked: @@ -883,7 +893,8 @@ function lifecycleHookToNodeFlag(lifecycleHook: LifecycleHooks): number { return nodeFlag; } -function elementBindingDefs(inputAsts: BoundElementPropertyAst[]): o.Expression[] { +function elementBindingDefs( + inputAsts: BoundElementPropertyAst[], dirAst: DirectiveAst): o.Expression[] { return inputAsts.map((inputAst) => { switch (inputAst.type) { case PropertyBindingType.Attribute: @@ -897,8 +908,11 @@ function elementBindingDefs(inputAsts: BoundElementPropertyAst[]): o.Expression[ o.literal(inputAst.securityContext) ]); case PropertyBindingType.Animation: + const bindingType = dirAst && dirAst.directive.isComponent ? + BindingType.ComponentHostProperty : + BindingType.ElementProperty; return o.literalArr([ - o.literal(BindingType.ElementProperty), o.literal('@' + inputAst.name), + o.literal(bindingType), o.literal('@' + inputAst.name), o.literal(inputAst.securityContext) ]); case PropertyBindingType.Class: @@ -1025,6 +1039,14 @@ function createComponentFactoryResolver(directives: DirectiveAst[]): ProviderAst return null; } -function eventName(eventAst: BoundEventAst): string { - return eventAst.isAnimation ? `@${eventAst.name}.${eventAst.phase}` : eventAst.name; +function elementEventNameAndTarget( + eventAst: BoundEventAst, dirAst: DirectiveAst): {name: string, target: string} { + if (eventAst.isAnimation) { + return { + name: `@${eventAst.name}.${eventAst.phase}`, + target: dirAst && dirAst.directive.isComponent ? 'component' : null + }; + } else { + return eventAst; + } } diff --git a/modules/@angular/core/src/view/element.ts b/modules/@angular/core/src/view/element.ts index 1daed8bad4..eac14c2242 100644 --- a/modules/@angular/core/src/view/element.ts +++ b/modules/@angular/core/src/view/element.ts @@ -7,9 +7,10 @@ */ import {isDevMode} from '../application_ref'; +import {RendererTypeV2, RendererV2} from '../render/api'; import {SecurityContext} from '../security'; -import {BindingDef, BindingType, DebugContext, DisposableFn, ElementData, ElementHandleEventFn, ElementOutputDef, NodeData, NodeDef, NodeFlags, NodeType, QueryValueType, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, asElementData} from './types'; +import {BindingDef, BindingType, DebugContext, DisposableFn, ElementData, ElementHandleEventFn, NodeData, NodeDef, NodeFlags, NodeType, OutputDef, OutputType, QueryValueType, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, asElementData, asProviderData} from './types'; import {checkAndUpdateBinding, dispatchEvent, elementEventFullName, filterQueryId, getParentRenderElement, resolveViewDefinition, sliceErrorStack, splitMatchedQueriesDsl, splitNamespace} from './util'; const NOOP: any = () => {}; @@ -34,20 +35,20 @@ export function anchorDef( parent: undefined, renderParent: undefined, bindingIndex: undefined, - disposableIndex: undefined, + outputIndex: undefined, // regular values flags, childFlags: 0, childMatchedQueries: 0, matchedQueries, matchedQueryIds, references, ngContentIndex, childCount, bindings: [], - disposableCount: 0, + outputs: [], element: { ns: undefined, name: undefined, - attrs: undefined, - outputs: [], template, source, - // will bet set by the view definition - component: undefined, + attrs: undefined, template, source, + componentProvider: undefined, + componentView: undefined, + componentRendererType: undefined, publicProviders: undefined, allProviders: undefined, handleEvent }, @@ -65,8 +66,13 @@ export function elementDef( fixedAttrs: [string, string][] = [], bindings?: ([BindingType.ElementClass, string] | [BindingType.ElementStyle, string, string] | - [BindingType.ElementAttribute | BindingType.ElementProperty, string, SecurityContext])[], - outputs?: (string | [string, string])[], handleEvent?: ElementHandleEventFn): NodeDef { + [ + BindingType.ElementAttribute | BindingType.ElementProperty | + BindingType.ComponentHostProperty, + string, SecurityContext + ])[], + outputs?: ([string, string])[], handleEvent?: ElementHandleEventFn, + componentView?: () => ViewDefinition, componentRendererType?: RendererTypeV2): NodeDef { if (!handleEvent) { handleEvent = NOOP; } @@ -93,29 +99,35 @@ export function elementDef( break; case BindingType.ElementAttribute: case BindingType.ElementProperty: + case BindingType.ComponentHostProperty: securityContext = entry[2]; break; } bindingDefs[i] = {type: bindingType, ns, name, nonMinifiedName: name, securityContext, suffix}; } outputs = outputs || []; - const outputDefs: ElementOutputDef[] = new Array(outputs.length); + const outputDefs: OutputDef[] = new Array(outputs.length); for (let i = 0; i < outputs.length; i++) { - const output = outputs[i]; - let target: string; - let eventName: string; - if (Array.isArray(output)) { - [target, eventName] = output; - } else { - eventName = output; - } - outputDefs[i] = {eventName: eventName, target: target}; + const [target, eventName] = outputs[i]; + outputDefs[i] = { + type: OutputType.ElementOutput, + target: target, eventName, + propName: undefined + }; } fixedAttrs = fixedAttrs || []; const attrs = <[string, string, string][]>fixedAttrs.map(([namespaceAndName, value]) => { const [ns, name] = splitNamespace(namespaceAndName); return [ns, name, value]; }); + // This is needed as the jit compiler always uses an empty hash as default RendererTypeV2, + // which is not filled for host views. + if (componentRendererType && componentRendererType.encapsulation == null) { + componentRendererType = null; + } + if (componentView) { + flags |= NodeFlags.HasComponent; + } return { type: NodeType.Element, // will bet set by the view definition @@ -124,21 +136,21 @@ export function elementDef( parent: undefined, renderParent: undefined, bindingIndex: undefined, - disposableIndex: undefined, + outputIndex: undefined, // regular values flags, childFlags: 0, childMatchedQueries: 0, matchedQueries, matchedQueryIds, references, ngContentIndex, childCount, bindings: bindingDefs, - disposableCount: outputDefs.length, + outputs: outputDefs, element: { ns, name, attrs, - outputs: outputDefs, source, + source, template: undefined, // will bet set by the view definition - component: undefined, + componentProvider: undefined, componentView, componentRendererType, publicProviders: undefined, allProviders: undefined, handleEvent, }, @@ -174,21 +186,24 @@ export function createElement(view: ViewData, renderHost: any, def: NodeDef): El renderer.setAttribute(el, name, value, ns); } } - if (elDef.outputs.length) { - for (let i = 0; i < elDef.outputs.length; i++) { - const output = elDef.outputs[i]; - 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; + return el; +} + +export function listenToElementOutputs(view: ViewData, compView: ViewData, def: NodeDef, el: any) { + for (let i = 0; i < def.outputs.length; i++) { + const output = def.outputs[i]; + const handleEventClosure = renderEventHandlerClosure( + view, def.index, elementEventFullName(output.target, output.eventName)); + let listenTarget = output.target; + let listenerView = view; + if (output.target === 'component') { + listenTarget = null; + listenerView = compView; } + const disposable = + listenerView.renderer.listen(listenTarget || el, output.eventName, handleEventClosure); + view.disposables[def.outputIndex + i] = disposable; } - return { - renderElement: el, - embeddedViews: (def.flags & NodeFlags.HasEmbeddedViews) ? [] : undefined, - projectedViews: undefined - }; } function renderEventHandlerClosure(view: ViewData, index: number, eventName: string) { @@ -223,7 +238,8 @@ function checkAndUpdateElementValue(view: ViewData, def: NodeDef, bindingIdx: nu return; } const binding = def.bindings[bindingIdx]; - const renderNode = asElementData(view, def.index).renderElement; + const elData = asElementData(view, def.index); + const renderNode = elData.renderElement; const name = binding.name; switch (binding.type) { case BindingType.ElementAttribute: @@ -238,6 +254,9 @@ function checkAndUpdateElementValue(view: ViewData, def: NodeDef, bindingIdx: nu case BindingType.ElementProperty: setElementProperty(view, binding, renderNode, name, value); break; + case BindingType.ComponentHostProperty: + setElementProperty(elData.componentView, binding, renderNode, name, value); + break; } } diff --git a/modules/@angular/core/src/view/ng_content.ts b/modules/@angular/core/src/view/ng_content.ts index 7473445d2f..0b518fc781 100644 --- a/modules/@angular/core/src/view/ng_content.ts +++ b/modules/@angular/core/src/view/ng_content.ts @@ -18,7 +18,7 @@ export function ngContentDef(ngContentIndex: number, index: number): NodeDef { parent: undefined, renderParent: undefined, bindingIndex: undefined, - disposableIndex: undefined, + outputIndex: undefined, // regular values flags: 0, childFlags: 0, @@ -28,7 +28,7 @@ export function ngContentDef(ngContentIndex: number, index: number): NodeDef { references: {}, ngContentIndex, childCount: 0, bindings: [], - disposableCount: 0, + outputs: [], element: undefined, provider: undefined, text: undefined, diff --git a/modules/@angular/core/src/view/provider.ts b/modules/@angular/core/src/view/provider.ts index 494148cb29..64a48a356f 100644 --- a/modules/@angular/core/src/view/provider.ts +++ b/modules/@angular/core/src/view/provider.ts @@ -15,7 +15,7 @@ import {ViewEncapsulation} from '../metadata/view'; import {Renderer as RendererV1, RendererFactoryV2, RendererTypeV2, RendererV2} from '../render/api'; 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 {BindingDef, BindingType, DepDef, DepFlags, DisposableFn, NodeData, NodeDef, NodeFlags, NodeType, OutputDef, OutputType, 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'; const RendererV1TokenKey = tokenKey(RendererV1); @@ -31,8 +31,7 @@ const NOT_CREATED = new Object(); export function directiveDef( flags: NodeFlags, matchedQueries: [string | number, QueryValueType][], childCount: number, ctor: any, deps: ([DepFlags, any] | any)[], props?: {[name: string]: [number, string]}, - outputs?: {[name: string]: string}, component?: () => ViewDefinition, - rendererType?: RendererTypeV2): NodeDef { + outputs?: {[name: string]: string}): NodeDef { const bindings: BindingDef[] = []; if (props) { for (let prop in props) { @@ -46,15 +45,16 @@ export function directiveDef( }; } } - const outputDefs: DirectiveOutputDef[] = []; + const outputDefs: OutputDef[] = []; if (outputs) { for (let propName in outputs) { - outputDefs.push({propName, eventName: outputs[propName]}); + outputDefs.push( + {type: OutputType.DirectiveOutput, propName, target: null, eventName: outputs[propName]}); } } return _def( NodeType.Directive, flags, matchedQueries, childCount, ProviderType.Class, ctor, ctor, deps, - bindings, outputDefs, component, rendererType); + bindings, outputDefs); } export function pipeDef(flags: NodeFlags, ctor: any, deps: ([DepFlags, any] | any)[]): NodeDef { @@ -70,14 +70,8 @@ export function providerDef( export function _def( type: NodeType, flags: NodeFlags, matchedQueriesDsl: [string | number, QueryValueType][], childCount: number, providerType: ProviderType, token: any, value: any, - deps: ([DepFlags, any] | any)[], bindings?: BindingDef[], outputs?: DirectiveOutputDef[], - component?: () => ViewDefinition, rendererType?: RendererTypeV2): NodeDef { + deps: ([DepFlags, any] | any)[], bindings?: BindingDef[], outputs?: OutputDef[]): NodeDef { const {matchedQueries, references, matchedQueryIds} = splitMatchedQueriesDsl(matchedQueriesDsl); - // This is needed as the jit compiler always uses an empty hash as default RendererTypeV2, - // which is not filled for host views. - if (rendererType && rendererType.encapsulation == null) { - rendererType = null; - } if (!outputs) { outputs = []; } @@ -96,9 +90,6 @@ export function _def( } return {flags, token, tokenKey: tokenKey(token)}; }); - if (component) { - flags = flags | NodeFlags.HasComponent; - } return { type, @@ -108,20 +99,14 @@ export function _def( parent: undefined, renderParent: undefined, bindingIndex: undefined, - disposableIndex: undefined, + outputIndex: undefined, // regular values flags, childFlags: 0, childMatchedQueries: 0, matchedQueries, matchedQueryIds, references, - ngContentIndex: undefined, childCount, bindings, - disposableCount: outputs.length, + ngContentIndex: undefined, childCount, bindings, outputs, element: undefined, - provider: { - type: providerType, - token, - tokenKey: tokenKey(token), value, - deps: depDefs, outputs, component, rendererType - }, + provider: {type: providerType, token, tokenKey: tokenKey(token), value, deps: depDefs}, text: undefined, pureExpression: undefined, query: undefined, @@ -149,17 +134,17 @@ export function createPipeInstance(view: ViewData, def: NodeDef): any { export function createDirectiveInstance(view: ViewData, def: NodeDef): any { // components can see other private services, other directives can't. - const allowPrivateServices = (def.flags & NodeFlags.HasComponent) > 0; + const allowPrivateServices = (def.flags & NodeFlags.IsComponent) > 0; const providerDef = def.provider; // directives are always eager and classes! const instance = createClass(view, def.parent, allowPrivateServices, def.provider.value, def.provider.deps); - if (providerDef.outputs.length) { - for (let i = 0; i < providerDef.outputs.length; i++) { - const output = providerDef.outputs[i]; + if (def.outputs.length) { + for (let i = 0; i < def.outputs.length; i++) { + const output = def.outputs[i]; const subscription = instance[output.propName].subscribe( eventHandlerClosure(view, def.parent.index, output.eventName)); - view.disposables[def.disposableIndex + i] = subscription.unsubscribe.bind(subscription); + view.disposables[def.outputIndex + i] = subscription.unsubscribe.bind(subscription); } } return instance; @@ -371,7 +356,7 @@ export function resolveDep( function findCompView(view: ViewData, elDef: NodeDef, allowPrivateServices: boolean) { let compView: ViewData; if (allowPrivateServices) { - compView = asProviderData(view, elDef.element.component.index).componentView; + compView = asElementData(view, elDef.index).componentView; } else { compView = view; while (compView.parent && !isComponentView(compView)) { @@ -396,8 +381,8 @@ function checkAndUpdateProp( changed = checkAndUpdateBinding(view, def, bindingIdx, value); } if (changed) { - if (def.flags & NodeFlags.HasComponent) { - const compView = providerData.componentView; + if (def.flags & NodeFlags.IsComponent) { + const compView = asElementData(view, def.parent.index).componentView; if (compView.def.flags & ViewFlags.OnPush) { compView.state |= ViewState.ChecksEnabled; } diff --git a/modules/@angular/core/src/view/pure_expression.ts b/modules/@angular/core/src/view/pure_expression.ts index a47f893e71..05f483add4 100644 --- a/modules/@angular/core/src/view/pure_expression.ts +++ b/modules/@angular/core/src/view/pure_expression.ts @@ -43,7 +43,7 @@ function _pureExpressionDef(type: PureExpressionType, propertyNames: string[]): parent: undefined, renderParent: undefined, bindingIndex: undefined, - disposableIndex: undefined, + outputIndex: undefined, // regular values flags: 0, childFlags: 0, @@ -53,7 +53,7 @@ function _pureExpressionDef(type: PureExpressionType, propertyNames: string[]): references: {}, ngContentIndex: undefined, childCount: 0, bindings, - disposableCount: 0, + outputs: [], element: undefined, provider: undefined, text: undefined, diff --git a/modules/@angular/core/src/view/query.ts b/modules/@angular/core/src/view/query.ts index 4a6e734a02..b643c6c8b3 100644 --- a/modules/@angular/core/src/view/query.ts +++ b/modules/@angular/core/src/view/query.ts @@ -31,7 +31,7 @@ export function queryDef( parent: undefined, renderParent: undefined, bindingIndex: undefined, - disposableIndex: undefined, + outputIndex: undefined, // regular values flags, childFlags: 0, @@ -42,7 +42,7 @@ export function queryDef( references: {}, childCount: 0, bindings: [], - disposableCount: 0, + outputs: [], element: undefined, provider: undefined, text: undefined, diff --git a/modules/@angular/core/src/view/refs.ts b/modules/@angular/core/src/view/refs.ts index 7d77a2b740..2246fb81c4 100644 --- a/modules/@angular/core/src/view/refs.ts +++ b/modules/@angular/core/src/view/refs.ts @@ -42,7 +42,7 @@ class ComponentFactory_ extends ComponentFactory { injector: Injector, projectableNodes: any[][] = null, rootSelectorOrNode: string|any = null): ComponentRef { const viewDef = resolveViewDefinition(this._viewClass); - const componentNodeIndex = viewDef.nodes[0].element.component.index; + const componentNodeIndex = viewDef.nodes[0].element.componentProvider.index; const view = Services.createRootView( injector, projectableNodes || [], rootSelectorOrNode, viewDef, EMPTY_CONTEXT); const component = asProviderData(view, componentNodeIndex).instance; @@ -255,7 +255,7 @@ export function createInjector(view: ViewData, elDef: NodeDef): Injector { class Injector_ implements Injector { constructor(private view: ViewData, private elDef: NodeDef) {} get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND): any { - const allowPrivateServices = !!this.elDef.element.component; + const allowPrivateServices = (this.elDef.flags & NodeFlags.HasComponent) !== 0; return Services.resolveDep( this.view, this.elDef, allowPrivateServices, {flags: DepFlags.None, token, tokenKey: tokenKey(token)}, notFoundValue); diff --git a/modules/@angular/core/src/view/services.ts b/modules/@angular/core/src/view/services.ts index c0d5f99783..ed75e27def 100644 --- a/modules/@angular/core/src/view/services.ts +++ b/modules/@angular/core/src/view/services.ts @@ -205,6 +205,7 @@ function debugCheckFn( const binding = nodeDef.bindings[i]; const value = values[i]; if ((binding.type === BindingType.ElementProperty || + binding.type === BindingType.ComponentHostProperty || binding.type === BindingType.DirectiveProperty) && checkBinding(view, nodeDef, i, value)) { bindingValues[normalizeDebugBindingName(binding.nonMinifiedName)] = @@ -273,7 +274,6 @@ class DebugContext_ implements DebugContext { private nodeDef: NodeDef; private elView: ViewData; private elDef: NodeDef; - private compProviderDef: NodeDef; constructor(public view: ViewData, public nodeIndex: number) { if (nodeIndex == null) { this.nodeIndex = nodeIndex = 0; @@ -292,21 +292,14 @@ class DebugContext_ implements DebugContext { } this.elDef = elDef; this.elView = elView; - this.compProviderDef = elView ? this.elDef.element.component : null; + } + private get elOrCompView() { + // Has to be done lazily as we use the DebugContext also during creation of elements... + return asElementData(this.elView, this.elDef.index).componentView || this.view; } get injector(): Injector { return createInjector(this.elView, this.elDef); } - get component(): any { - if (this.compProviderDef) { - return asProviderData(this.elView, this.compProviderDef.index).instance; - } - return this.view.component; - } - get context(): any { - if (this.compProviderDef) { - return asProviderData(this.elView, this.compProviderDef.index).instance; - } - return this.view.context; - } + get component(): any { return this.elOrCompView.component; } + get context(): any { return this.elOrCompView.context; } get providerTokens(): any[] { const tokens: any[] = []; if (this.elDef) { @@ -343,10 +336,7 @@ class DebugContext_ implements DebugContext { } } get componentRenderElement() { - const view = this.compProviderDef ? - asProviderData(this.elView, this.compProviderDef.index).componentView : - this.view; - const elData = findHostElement(view); + const elData = findHostElement(this.elOrCompView); return elData ? elData.renderElement : undefined; } get renderNode(): any { diff --git a/modules/@angular/core/src/view/text.ts b/modules/@angular/core/src/view/text.ts index ef8056ae71..5c1da286d9 100644 --- a/modules/@angular/core/src/view/text.ts +++ b/modules/@angular/core/src/view/text.ts @@ -34,7 +34,7 @@ export function textDef(ngContentIndex: number, constants: string[]): NodeDef { parent: undefined, renderParent: undefined, bindingIndex: undefined, - disposableIndex: undefined, + outputIndex: undefined, // regular values flags: 0, childFlags: 0, @@ -43,7 +43,7 @@ export function textDef(ngContentIndex: number, constants: string[]): NodeDef { matchedQueryIds: 0, references: {}, ngContentIndex, childCount: 0, bindings, - disposableCount: 0, + outputs: [], element: undefined, provider: undefined, text: {prefix: constants[0], source}, diff --git a/modules/@angular/core/src/view/types.ts b/modules/@angular/core/src/view/types.ts index 92c6b25980..36951c4036 100644 --- a/modules/@angular/core/src/view/types.ts +++ b/modules/@angular/core/src/view/types.ts @@ -40,7 +40,7 @@ export interface ViewDefinition { reverseChildNodes: NodeDef[]; lastRenderRootNode: NodeDef; bindingCount: number; - disposableCount: number; + outputCount: number; /** * Binary or of all query ids that are matched by one of the nodes. * This includes query ids from templates as well. @@ -99,8 +99,8 @@ export interface NodeDef { bindingIndex: number; bindings: BindingDef[]; - disposableIndex: number; - disposableCount: number; + outputIndex: number; + outputs: OutputDef[]; /** * references that the user placed on the element */ @@ -151,12 +151,13 @@ export enum NodeFlags { AfterViewChecked = 1 << 7, HasEmbeddedViews = 1 << 8, HasComponent = 1 << 9, - HasContentQuery = 1 << 10, - HasStaticQuery = 1 << 11, - HasDynamicQuery = 1 << 12, - HasViewQuery = 1 << 13, - LazyProvider = 1 << 14, - PrivateProvider = 1 << 15, + IsComponent = 1 << 10, + HasContentQuery = 1 << 11, + HasStaticQuery = 1 << 12, + HasDynamicQuery = 1 << 13, + HasViewQuery = 1 << 14, + LazyProvider = 1 << 15, + PrivateProvider = 1 << 16, } export interface BindingDef { @@ -173,11 +174,24 @@ export enum BindingType { ElementClass, ElementStyle, ElementProperty, + ComponentHostProperty, DirectiveProperty, TextInterpolation, PureExpressionProperty } +export interface OutputDef { + type: OutputType; + target: 'window'|'document'|'body'|'component'; + eventName: string; + propName: string; +} + +export enum OutputType { + ElementOutput, + DirectiveOutput +} + export enum QueryValueType { ElementRef, RenderElement, @@ -191,9 +205,11 @@ export interface ElementDef { ns: string; /** ns, name, value */ attrs: [string, string, string][]; - outputs: ElementOutputDef[]; template: ViewDefinition; - component: NodeDef; + componentProvider: NodeDef; + componentRendererType: RendererTypeV2; + // closure to allow recursive components + componentView: ViewDefinitionFactory; /** * visible public providers for DI in the view, * as see from this element. This does not include private providers. @@ -208,11 +224,6 @@ export interface ElementDef { handleEvent: ElementHandleEventFn; } -export interface ElementOutputDef { - target: string; - eventName: string; -} - export type ElementHandleEventFn = (view: ViewData, eventName: string, event: any) => boolean; export interface ProviderDef { @@ -221,10 +232,6 @@ export interface ProviderDef { tokenKey: string; value: any; deps: DepDef[]; - outputs: DirectiveOutputDef[]; - rendererType: RendererTypeV2; - // closure to allow recursive components - component: ViewDefinitionFactory; } export enum ProviderType { @@ -250,11 +257,6 @@ export enum DepFlags { Value = 2 << 2, } -export interface DirectiveOutputDef { - propName: string; - eventName: string; -} - export interface TextDef { prefix: string; source: string; @@ -369,6 +371,7 @@ export function asTextData(view: ViewData, index: number): TextData { */ export interface ElementData { renderElement: any; + componentView: ViewData; embeddedViews: ViewData[]; // views that have been created from the template // of this element, @@ -389,10 +392,7 @@ export function asElementData(view: ViewData, index: number): ElementData { * * Attention: Adding fields to this is performance sensitive! */ -export interface ProviderData { - instance: any; - componentView: ViewData; -} +export interface ProviderData { instance: any; } /** * Accessor for view.nodes, enforcing that every usage site stays monomorphic. diff --git a/modules/@angular/core/src/view/util.ts b/modules/@angular/core/src/view/util.ts index d116269d4d..3e71d7ee1a 100644 --- a/modules/@angular/core/src/view/util.ts +++ b/modules/@angular/core/src/view/util.ts @@ -172,10 +172,10 @@ export function getParentRenderElement(view: ViewData, renderHost: any, def: Nod let renderParent = def.renderParent; if (renderParent) { const parent = def.parent; - if (parent && (parent.type !== NodeType.Element || !parent.element.component || - (parent.element.component.provider.rendererType && - parent.element.component.provider.rendererType.encapsulation === - ViewEncapsulation.Native))) { + if (parent && + (parent.type !== NodeType.Element || (parent.flags & NodeFlags.HasComponent) === 0 || + (parent.element.componentRendererType && + parent.element.componentRendererType.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; diff --git a/modules/@angular/core/src/view/view.ts b/modules/@angular/core/src/view/view.ts index 5cfa69ca57..f1a91a7fdf 100644 --- a/modules/@angular/core/src/view/view.ts +++ b/modules/@angular/core/src/view/view.ts @@ -9,14 +9,14 @@ import {ViewEncapsulation} from '../metadata/view'; import {RendererTypeV2, RendererV2} from '../render/api'; -import {checkAndUpdateElementDynamic, checkAndUpdateElementInline, createElement} from './element'; +import {checkAndUpdateElementDynamic, checkAndUpdateElementInline, createElement, listenToElementOutputs} from './element'; import {expressionChangedAfterItHasBeenCheckedError} from './errors'; import {appendNgContent} from './ng_content'; import {callLifecycleHooksChildrenFirst, checkAndUpdateDirectiveDynamic, checkAndUpdateDirectiveInline, createDirectiveInstance, createPipeInstance, createProviderInstance} from './provider'; import {checkAndUpdatePureExpressionDynamic, checkAndUpdatePureExpressionInline, createPureExpression} from './pure_expression'; import {checkAndUpdateQuery, createQuery, queryDef} from './query'; import {checkAndUpdateTextDynamic, checkAndUpdateTextInline, createText} from './text'; -import {ArgumentType, ElementDef, NodeData, NodeDef, NodeFlags, NodeType, ProviderData, ProviderDef, RootData, Services, TextDef, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewHandleEventFn, ViewState, ViewUpdateFn, asElementData, asProviderData, asPureExpressionData, asQueryList, asTextData} from './types'; +import {ArgumentType, ElementData, ElementDef, NodeData, NodeDef, NodeFlags, NodeType, ProviderData, ProviderDef, RootData, Services, TextDef, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewHandleEventFn, ViewState, ViewUpdateFn, asElementData, asProviderData, asPureExpressionData, asQueryList, asTextData} from './types'; import {checkBindingNoChanges, isComponentView, resolveViewDefinition, viewParentEl} from './util'; const NOOP = (): any => undefined; @@ -51,7 +51,7 @@ export function viewDef( node.index = i; node.parent = currentParent; node.bindingIndex = viewBindingCount; - node.disposableIndex = viewDisposableCount; + node.outputIndex = viewDisposableCount; node.reverseChildIndex = calculateReverseChildIndex(currentParent, i, node.childCount, nodes.length); @@ -90,7 +90,7 @@ export function viewDef( } viewBindingCount += node.bindings.length; - viewDisposableCount += node.disposableCount; + viewDisposableCount += node.outputs.length; if (!currentRenderParent && (node.type === NodeType.Element || node.type === NodeType.Text)) { lastRenderRootNode = node; @@ -104,7 +104,7 @@ export function viewDef( currentParent.element.allProviders = currentParent.element.publicProviders; } const isPrivateService = (node.flags & NodeFlags.PrivateProvider) !== 0; - const isComponent = (node.flags & NodeFlags.HasComponent) !== 0; + const isComponent = (node.flags & NodeFlags.IsComponent) !== 0; if (!isPrivateService || isComponent) { currentParent.element.publicProviders[node.provider.tokenKey] = node; } else { @@ -116,7 +116,7 @@ export function viewDef( currentParent.element.allProviders[node.provider.tokenKey] = node; } if (isComponent) { - currentParent.element.component = node; + currentParent.element.componentProvider = node; } } if (node.childCount) { @@ -141,7 +141,7 @@ export function viewDef( updateRenderer: updateRenderer || NOOP, handleEvent: handleEvent || NOOP, bindingCount: viewBindingCount, - disposableCount: viewDisposableCount, lastRenderRootNode + outputCount: viewDisposableCount, lastRenderRootNode }; } @@ -242,7 +242,7 @@ function createView( root: RootData, renderer: RendererV2, parent: ViewData, parentNodeDef: NodeDef, def: ViewDefinition): ViewData { const nodes: NodeData[] = new Array(def.nodes.length); - const disposables = def.disposableCount ? new Array(def.disposableCount) : undefined; + const disposables = def.outputCount ? new Array(def.outputCount) : undefined; const view: ViewData = { def, parent, @@ -271,63 +271,66 @@ function createViewNodes(view: ViewData) { for (let i = 0; i < def.nodes.length; i++) { const nodeDef = def.nodes[i]; Services.setCurrentNode(view, i); + let nodeData: any; switch (nodeDef.type) { case NodeType.Element: - nodes[i] = createElement(view, renderHost, nodeDef) as any; - break; - case NodeType.Text: - nodes[i] = createText(view, renderHost, nodeDef) as any; - break; - case NodeType.Provider: { - const instance = createProviderInstance(view, nodeDef); - const providerData = {componentView: undefined, instance}; - nodes[i] = providerData as any; - break; - } - case NodeType.Pipe: { - const instance = createPipeInstance(view, nodeDef); - const providerData = {componentView: undefined, instance}; - nodes[i] = providerData as any; - break; - } - case NodeType.Directive: { + const el = createElement(view, renderHost, nodeDef) as any; + let componentView: ViewData; if (nodeDef.flags & NodeFlags.HasComponent) { - // Components can inject a ChangeDetectorRef that needs a references to - // the component view. Therefore, we create the component view first - // and set the ProviderData in ViewData, and then instantiate the provider. - const compViewDef = resolveViewDefinition(nodeDef.provider.component); - const rendererType = nodeDef.provider.rendererType; + const compViewDef = resolveViewDefinition(nodeDef.element.componentView); + const rendererType = nodeDef.element.componentRendererType; let compRenderer: RendererV2; if (!rendererType) { compRenderer = view.root.renderer; } else { - const hostEl = asElementData(view, nodeDef.parent.index).renderElement; - compRenderer = view.root.rendererFactory.createRenderer(hostEl, rendererType); + compRenderer = view.root.rendererFactory.createRenderer(el, rendererType); } - const componentView = createView(view.root, compRenderer, view, nodeDef, compViewDef); - const providerData = {componentView, instance: undefined}; - nodes[i] = providerData as any; - const instance = providerData.instance = createDirectiveInstance(view, nodeDef); + componentView = createView( + view.root, compRenderer, view, nodeDef.element.componentProvider, compViewDef); + } + listenToElementOutputs(view, componentView, nodeDef, el); + nodeData = { + renderElement: el, + componentView, + embeddedViews: (nodeDef.flags & NodeFlags.HasEmbeddedViews) ? [] : undefined, + projectedViews: undefined + }; + break; + case NodeType.Text: + nodeData = createText(view, renderHost, nodeDef) as any; + break; + case NodeType.Provider: { + const instance = createProviderInstance(view, nodeDef); + nodeData = {instance}; + break; + } + case NodeType.Pipe: { + const instance = createPipeInstance(view, nodeDef); + nodeData = {instance}; + break; + } + case NodeType.Directive: { + const instance = createDirectiveInstance(view, nodeDef); + nodeData = {instance}; + if (nodeDef.flags & NodeFlags.IsComponent) { + const compView = asElementData(view, nodeDef.parent.index).componentView; initView(componentView, instance, instance); - } else { - const instance = createDirectiveInstance(view, nodeDef); - const providerData = {componentView: undefined, instance}; - nodes[i] = providerData as any; } break; } case NodeType.PureExpression: - nodes[i] = createPureExpression(view, nodeDef) as any; + nodeData = createPureExpression(view, nodeDef) as any; break; case NodeType.Query: - nodes[i] = createQuery() as any; + nodeData = createQuery() as any; break; case NodeType.NgContent: appendNgContent(view, renderHost, nodeDef); // no runtime data needed for NgContent... - nodes[i] = undefined; + nodeData = undefined; break; } + nodes[i] = nodeData; } // Create the ViewData.nodes of component views after we created everything else, // so that e.g. ng-content works @@ -479,7 +482,7 @@ export function destroyView(view: ViewData) { if (view.renderer.destroyNode) { destroyViewNodes(view); } - if (view.parentNodeDef && view.parentNodeDef.flags & NodeFlags.HasComponent) { + if (isComponentView(view)) { view.renderer.destroy(); } view.state |= ViewState.Destroyed; @@ -513,8 +516,7 @@ function execComponentViewsAction(view: ViewData, action: ViewAction) { const nodeDef = def.nodes[i]; if (nodeDef.flags & NodeFlags.HasComponent) { // a leaf - const providerData = asProviderData(view, i); - callViewAction(providerData.componentView, action); + callViewAction(asElementData(view, i).componentView, action); } else if ((nodeDef.childFlags & NodeFlags.HasComponent) === 0) { // a parent with leafs // no child is a component, diff --git a/modules/@angular/core/test/animation/animation_integration_next_spec.ts b/modules/@angular/core/test/animation/animation_integration_next_spec.ts index d01a8f7029..99241bbb42 100644 --- a/modules/@angular/core/test/animation/animation_integration_next_spec.ts +++ b/modules/@angular/core/test/animation/animation_integration_next_spec.ts @@ -81,42 +81,41 @@ function declareTests({useJit}: {useJit: boolean}) { ]); }); - xit('should trigger a state change animation from void => state on the component host element', - () => { - @Component({ - selector: 'my-cmp', - template: '...', - animations: [trigger( - 'myAnimation', - [transition( - 'a => b', - [style({'opacity': '0'}), animate(500, style({'opacity': '1'}))])])], - }) - class Cmp { - @HostBinding('@myAnimation') - get binding() { return this.exp ? 'b' : 'a'; } - exp: any = false; - } + it('should trigger a state change animation from void => state on the component host element', + () => { + @Component({ + selector: 'my-cmp', + template: '...', + animations: [trigger( + 'myAnimation', + [transition( + 'a => b', [style({'opacity': '0'}), animate(500, style({'opacity': '1'}))])])], + }) + class Cmp { + @HostBinding('@myAnimation') + get binding() { return this.exp ? 'b' : 'a'; } + exp: any = false; + } - TestBed.configureTestingModule({declarations: [Cmp]}); + TestBed.configureTestingModule({declarations: [Cmp]}); - const engine = TestBed.get(ɵAnimationEngine); - const fixture = TestBed.createComponent(Cmp); - const cmp = fixture.componentInstance; - cmp.exp = false; - fixture.detectChanges(); - engine.flush(); - expect(getLog().length).toEqual(0); + const engine = TestBed.get(ɵAnimationEngine); + const fixture = TestBed.createComponent(Cmp); + const cmp = fixture.componentInstance; + cmp.exp = false; + fixture.detectChanges(); + engine.flush(); + expect(getLog().length).toEqual(0); - cmp.exp = true; - fixture.detectChanges(); - engine.flush(); - expect(getLog().length).toEqual(1); + cmp.exp = true; + fixture.detectChanges(); + engine.flush(); + expect(getLog().length).toEqual(1); - const data = getLog().pop(); - expect(data.element).toEqual(fixture.elementRef.nativeElement); - expect(data.keyframes).toEqual([{offset: 0, opacity: '0'}, {offset: 1, opacity: '1'}]); - }); + const data = getLog().pop(); + expect(data.element).toEqual(fixture.elementRef.nativeElement); + expect(data.keyframes).toEqual([{offset: 0, opacity: '0'}, {offset: 1, opacity: '1'}]); + }); it('should cancel and merge in mid-animation styles into the follow-up animation', () => { @Component({ @@ -516,42 +515,42 @@ function declareTests({useJit}: {useJit: boolean}) { expect(cmp.event2.triggerName).toBeTruthy('ani2'); }); - xit('should trigger a state change listener for when the animation changes state from void => state on the host element', - () => { - @Component({ - selector: 'my-cmp', - template: `...`, - animations: [trigger( - 'myAnimation2', - [transition( - 'void => *', - [style({'opacity': '0'}), animate(1000, style({'opacity': '1'}))])])], - }) - class Cmp { - event: AnimationEvent; + it('should trigger a state change listener for when the animation changes state from void => state on the host element', + () => { + @Component({ + selector: 'my-cmp', + template: `...`, + animations: [trigger( + 'myAnimation2', + [transition( + 'void => *', + [style({'opacity': '0'}), animate(1000, style({'opacity': '1'}))])])], + }) + class Cmp { + event: AnimationEvent; - @HostBinding('@myAnimation2') - exp: any = false; + @HostBinding('@myAnimation2') + exp: any = false; - @HostListener('@myAnimation2.start') - callback = (event: any) => { this.event = event; }; - } + @HostListener('@myAnimation2.start', ['$event']) + callback = (event: any) => { this.event = event; }; + } - TestBed.configureTestingModule({declarations: [Cmp]}); + TestBed.configureTestingModule({declarations: [Cmp]}); - const engine = TestBed.get(ɵAnimationEngine); - const fixture = TestBed.createComponent(Cmp); - const cmp = fixture.componentInstance; - cmp.exp = 'TRUE'; - fixture.detectChanges(); - engine.flush(); + const engine = TestBed.get(ɵAnimationEngine); + const fixture = TestBed.createComponent(Cmp); + const cmp = fixture.componentInstance; + cmp.exp = 'TRUE'; + fixture.detectChanges(); + engine.flush(); - expect(cmp.event.triggerName).toEqual('myAnimation2'); - expect(cmp.event.phaseName).toEqual('start'); - expect(cmp.event.totalTime).toEqual(1000); - expect(cmp.event.fromState).toEqual('void'); - expect(cmp.event.toState).toEqual('TRUE'); - }); + expect(cmp.event.triggerName).toEqual('myAnimation2'); + expect(cmp.event.phaseName).toEqual('start'); + expect(cmp.event.totalTime).toEqual(1000); + expect(cmp.event.fromState).toEqual('void'); + expect(cmp.event.toState).toEqual('TRUE'); + }); }); }); } diff --git a/modules/@angular/core/test/view/component_view_spec.ts b/modules/@angular/core/test/view/component_view_spec.ts index 0172189516..82571998dd 100644 --- a/modules/@angular/core/test/view/component_view_spec.ts +++ b/modules/@angular/core/test/view/component_view_spec.ts @@ -7,294 +7,306 @@ */ import {Injector, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation} from '@angular/core'; -import {ArgumentType, BindingType, NodeCheckFn, NodeDef, NodeFlags, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewState, ViewUpdateFn, anchorDef, asProviderData, directiveDef, elementDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index'; +import {ArgumentType, BindingType, NodeCheckFn, NodeDef, NodeFlags, OutputType, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewState, ViewUpdateFn, anchorDef, asElementData, asProviderData, directiveDef, elementDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {createRootView, isBrowser, removeNodes} from './helper'; export function main() { - describe(`Component Views`, () => { - function compViewDef( - nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn, - viewFlags: ViewFlags = ViewFlags.None): ViewDefinition { - return viewDef(viewFlags, nodes, updateDirectives, updateRenderer); - } - - function createAndGetRootNodes(viewDef: ViewDefinition): {rootNodes: any[], view: ViewData} { - const view = createRootView(viewDef); - const rootNodes = rootRenderNodes(view); - return {rootNodes, view}; - } - - it('should create and attach component views', () => { - let instance: AComp; - class AComp { - constructor() { instance = this; } - } - - const {view, rootNodes} = createAndGetRootNodes(compViewDef([ - elementDef(NodeFlags.None, null, null, 1, 'div'), - directiveDef( - NodeFlags.None, null, 0, AComp, [], null, null, - () => compViewDef([ - elementDef(NodeFlags.None, null, null, 0, 'span'), - ])), - ])); - - const compView = asProviderData(view, 1).componentView; - - expect(compView.context).toBe(instance); - expect(compView.component).toBe(instance); - - const compRootEl = getDOM().childNodes(rootNodes[0])[0]; - expect(getDOM().nodeName(compRootEl).toLowerCase()).toBe('span'); - }); - - if (isBrowser()) { - describe('root views', () => { - let rootNode: HTMLElement; - beforeEach(() => { - rootNode = document.createElement('root'); - document.body.appendChild(rootNode); - removeNodes.push(rootNode); - }); - - it('should select root elements based on a selector', () => { - const view = createRootView( - compViewDef([ - elementDef(NodeFlags.None, null, null, 0, 'div'), - ]), - {}, [], 'root'); - const rootNodes = rootRenderNodes(view); - expect(rootNodes).toEqual([rootNode]); - }); - - it('should select root elements based on a node', () => { - const view = createRootView( - compViewDef([ - elementDef(NodeFlags.None, null, null, 0, 'div'), - ]), - {}, [], rootNode); - const rootNodes = rootRenderNodes(view); - expect(rootNodes).toEqual([rootNode]); - }); - - it('should set attributes on the root node', () => { - const view = createRootView( - compViewDef([ - elementDef(NodeFlags.None, null, null, 0, 'div', [['a', 'b']]), - ]), - {}, [], rootNode); - expect(rootNode.getAttribute('a')).toBe('b'); - }); - - it('should clear the content of the root node', () => { - rootNode.appendChild(document.createElement('div')); - const view = createRootView( - compViewDef([ - elementDef(NodeFlags.None, null, null, 0, 'div', [['a', 'b']]), - ]), - {}, [], rootNode); - expect(rootNode.childNodes.length).toBe(0); - }); - }); - } - - describe('data binding', () => { - it('should dirty check component views', () => { - let value: any; - class AComp { - a: any; + describe( + `Component Views`, () => { + function compViewDef( + nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn, + viewFlags: ViewFlags = ViewFlags.None): ViewDefinition { + return viewDef(viewFlags, nodes, updateDirectives, updateRenderer); } - const update = - jasmine.createSpy('updater').and.callFake((check: NodeCheckFn, view: ViewData) => { - check(view, 0, ArgumentType.Inline, value); + function createAndGetRootNodes(viewDef: ViewDefinition): + {rootNodes: any[], view: ViewData} { + const view = createRootView(viewDef); + const rootNodes = rootRenderNodes(view); + return {rootNodes, view}; + } + + it('should create and attach component views', () => { + let instance: AComp; + class AComp { + constructor() { instance = this; } + } + + const {view, rootNodes} = createAndGetRootNodes(compViewDef([ + elementDef( + NodeFlags.None, null, null, 1, 'div', null, null, null, null, + () => compViewDef([ + elementDef(NodeFlags.None, null, null, 0, 'span'), + ])), + directiveDef(NodeFlags.IsComponent, null, 0, AComp, []), + ])); + + const compView = asElementData(view, 0).componentView; + + expect(compView.context).toBe(instance); + expect(compView.component).toBe(instance); + + const compRootEl = getDOM().childNodes(rootNodes[0])[0]; + expect(getDOM().nodeName(compRootEl).toLowerCase()).toBe('span'); + }); + + if (isBrowser()) { + describe('root views', () => { + let rootNode: HTMLElement; + beforeEach(() => { + rootNode = document.createElement('root'); + document.body.appendChild(rootNode); + removeNodes.push(rootNode); }); - const {view, rootNodes} = createAndGetRootNodes( + it('should select root elements based on a selector', () => { + const view = createRootView( + compViewDef([ + elementDef(NodeFlags.None, null, null, 0, 'div'), + ]), + {}, [], 'root'); + const rootNodes = rootRenderNodes(view); + expect(rootNodes).toEqual([rootNode]); + }); + + it('should select root elements based on a node', () => { + const view = createRootView( + compViewDef([ + elementDef(NodeFlags.None, null, null, 0, 'div'), + ]), + {}, [], rootNode); + const rootNodes = rootRenderNodes(view); + expect(rootNodes).toEqual([rootNode]); + }); + + it('should set attributes on the root node', () => { + const view = createRootView( + compViewDef([ + elementDef(NodeFlags.None, null, null, 0, 'div', [['a', 'b']]), + ]), + {}, [], rootNode); + expect(rootNode.getAttribute('a')).toBe('b'); + }); + + it('should clear the content of the root node', () => { + rootNode.appendChild(document.createElement('div')); + const view = createRootView( + compViewDef([ + elementDef(NodeFlags.None, null, null, 0, 'div', [['a', 'b']]), + ]), + {}, [], rootNode); + expect(rootNode.childNodes.length).toBe(0); + }); + }); + } + + describe( + 'data binding', () => { + it('should dirty check component views', + () => { + let value: any; + class AComp { + a: any; + } + + const update = jasmine.createSpy('updater').and.callFake( + (check: NodeCheckFn, view: ViewData) => { + check(view, 0, ArgumentType.Inline, value); + }); + + const {view, rootNodes} = createAndGetRootNodes( compViewDef([ - elementDef(NodeFlags.None, null, null, 1, 'div'), - directiveDef(NodeFlags.None, null, 0, AComp, [], null, null, () => compViewDef( + elementDef(NodeFlags.None, null, null, 1, 'div', null, null, null, null, () => compViewDef( [ elementDef(NodeFlags.None, null, null, 0, 'span', null, [[BindingType.ElementAttribute, 'a', SecurityContext.NONE]]), ], null, update )), + directiveDef(NodeFlags.IsComponent, null, 0, AComp, []), ])); - const compView = asProviderData(view, 1).componentView; + const compView = asElementData(view, 0).componentView; - value = 'v1'; - Services.checkAndUpdateView(view); + value = 'v1'; + Services.checkAndUpdateView(view); - expect(update.calls.mostRecent().args[1]).toBe(compView); + expect(update.calls.mostRecent().args[1]).toBe(compView); - update.calls.reset(); - Services.checkNoChangesView(view); + update.calls.reset(); + Services.checkNoChangesView(view); - expect(update.calls.mostRecent().args[1]).toBe(compView); + expect(update.calls.mostRecent().args[1]).toBe(compView); - value = 'v2'; - expect(() => Services.checkNoChangesView(view)) - .toThrowError( - `ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'v1'. Current value: 'v2'.`); - }); + value = 'v2'; + expect(() => Services.checkNoChangesView(view)) + .toThrowError( + `ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'v1'. Current value: 'v2'.`); + }); - it('should support detaching and attaching component views for dirty checking', () => { - class AComp { - a: any; - } + it('should support detaching and attaching component views for dirty checking', + () => { + class AComp { + a: any; + } - const update = jasmine.createSpy('updater'); + const update = jasmine.createSpy('updater'); - const {view, rootNodes} = createAndGetRootNodes(compViewDef([ - elementDef(NodeFlags.None, null, null, 1, 'div'), - directiveDef( - NodeFlags.None, null, 0, AComp, [], null, null, - () => compViewDef( - [ - elementDef(NodeFlags.None, null, null, 0, 'span'), - ], - update)), - ])); + const {view, rootNodes} = createAndGetRootNodes(compViewDef([ + elementDef( + NodeFlags.None, null, null, 1, 'div', null, null, null, null, + () => compViewDef( + [ + elementDef(NodeFlags.None, null, null, 0, 'span'), + ], + update)), + directiveDef(NodeFlags.IsComponent, null, 0, AComp, [], null, null), + ])); - const compView = asProviderData(view, 1).componentView; + const compView = asElementData(view, 0).componentView; - Services.checkAndUpdateView(view); - update.calls.reset(); + Services.checkAndUpdateView(view); + update.calls.reset(); - compView.state &= ~ViewState.ChecksEnabled; - Services.checkAndUpdateView(view); - expect(update).not.toHaveBeenCalled(); + compView.state &= ~ViewState.ChecksEnabled; + Services.checkAndUpdateView(view); + expect(update).not.toHaveBeenCalled(); - compView.state |= ViewState.ChecksEnabled; - Services.checkAndUpdateView(view); - expect(update).toHaveBeenCalled(); - }); + compView.state |= ViewState.ChecksEnabled; + Services.checkAndUpdateView(view); + expect(update).toHaveBeenCalled(); + }); - if (isBrowser()) { - it('should support OnPush components', () => { - let compInputValue: any; - class AComp { - a: any; - } + if (isBrowser()) { + it('should support OnPush components', () => { + let compInputValue: any; + class AComp { + a: any; + } - const update = jasmine.createSpy('updater'); + const update = jasmine.createSpy('updater'); - const addListenerSpy = spyOn(HTMLElement.prototype, 'addEventListener').and.callThrough(); + const addListenerSpy = + spyOn(HTMLElement.prototype, 'addEventListener').and.callThrough(); - const {view} = createAndGetRootNodes(compViewDef( - [ - elementDef(NodeFlags.None, null, null, 1, 'div'), - directiveDef( - NodeFlags.None, null, 0, AComp, [], {a: [0, 'a']}, null, - () => compViewDef( - [ - elementDef(NodeFlags.None, null, null, 0, 'span', null, null, ['click']), - ], - update, null, ViewFlags.OnPush)), - ], - (check, view) => { check(view, 1, ArgumentType.Inline, compInputValue); })); + const {view} = createAndGetRootNodes(compViewDef( + [ + elementDef( + NodeFlags.None, null, null, 1, 'div', null, null, null, null, + () => { + return compViewDef( + [ + elementDef( + NodeFlags.None, null, null, 0, 'span', null, null, + [[null, 'click']]), + ], + update, null, ViewFlags.OnPush); + }), + directiveDef(NodeFlags.IsComponent, null, 0, AComp, [], {a: [0, 'a']}), + ], + (check, view) => { check(view, 1, ArgumentType.Inline, compInputValue); })); - Services.checkAndUpdateView(view); + Services.checkAndUpdateView(view); - // auto detach - update.calls.reset(); - Services.checkAndUpdateView(view); - expect(update).not.toHaveBeenCalled(); + // auto detach + update.calls.reset(); + Services.checkAndUpdateView(view); + expect(update).not.toHaveBeenCalled(); - // auto attach on input changes - update.calls.reset(); - compInputValue = 'v1'; - Services.checkAndUpdateView(view); - expect(update).toHaveBeenCalled(); + // auto attach on input changes + update.calls.reset(); + compInputValue = 'v1'; + Services.checkAndUpdateView(view); + expect(update).toHaveBeenCalled(); - // auto detach - update.calls.reset(); - Services.checkAndUpdateView(view); - expect(update).not.toHaveBeenCalled(); + // auto detach + update.calls.reset(); + Services.checkAndUpdateView(view); + expect(update).not.toHaveBeenCalled(); - // auto attach on events - addListenerSpy.calls.mostRecent().args[1]('SomeEvent'); - update.calls.reset(); - Services.checkAndUpdateView(view); - expect(update).toHaveBeenCalled(); + // auto attach on events + addListenerSpy.calls.mostRecent().args[1]('SomeEvent'); + update.calls.reset(); + Services.checkAndUpdateView(view); + expect(update).toHaveBeenCalled(); - // auto detach - update.calls.reset(); - Services.checkAndUpdateView(view); - expect(update).not.toHaveBeenCalled(); - }); - } + // auto detach + update.calls.reset(); + Services.checkAndUpdateView(view); + expect(update).not.toHaveBeenCalled(); + }); + } - it('should stop dirty checking views that threw errors in change detection', () => { - class AComp { - a: any; - } + it('should stop dirty checking views that threw errors in change detection', + () => { + class AComp { + a: any; + } - const update = jasmine.createSpy('updater'); + const update = jasmine.createSpy('updater'); - const {view, rootNodes} = createAndGetRootNodes(compViewDef([ - elementDef(NodeFlags.None, null, null, 1, 'div'), - directiveDef( - NodeFlags.None, null, 0, AComp, [], null, null, - () => compViewDef( + const {view, rootNodes} = createAndGetRootNodes(compViewDef([ + elementDef(NodeFlags.None, null, null, 1, 'div', null, null, null, null, () => compViewDef( [ elementDef(NodeFlags.None, null, null, 0, 'span', null, [[BindingType.ElementAttribute, 'a', SecurityContext.NONE]]), ], null, update)), - ])); - - const compView = asProviderData(view, 1).componentView; - - update.and.callFake((check: NodeCheckFn, view: ViewData) => { throw new Error('Test'); }); - expect(() => Services.checkAndUpdateView(view)).toThrowError('Test'); - expect(update).toHaveBeenCalled(); - - update.calls.reset(); - Services.checkAndUpdateView(view); - expect(update).not.toHaveBeenCalled(); - }); - - }); - - describe('destroy', () => { - it('should destroy component views', () => { - const log: string[] = []; - - class AComp {} - - class ChildProvider { - ngOnDestroy() { log.push('ngOnDestroy'); }; - } - - const {view, rootNodes} = createAndGetRootNodes(compViewDef([ - elementDef(NodeFlags.None, null, null, 1, 'div'), directiveDef( - NodeFlags.None, null, 0, AComp, [], null, null, - () => compViewDef([ - elementDef(NodeFlags.None, null, null, 1, 'span'), - directiveDef(NodeFlags.OnDestroy, null, 0, ChildProvider, []) - ])), + NodeFlags.IsComponent, null, 0, AComp, [], null, null, + ), ])); - Services.destroyView(view); + const compView = asElementData(view, 0).componentView; + + update.and.callFake( + (check: NodeCheckFn, view: ViewData) => { throw new Error('Test'); }); + expect(() => Services.checkAndUpdateView(view)).toThrowError('Test'); + expect(update).toHaveBeenCalled(); + + update.calls.reset(); + Services.checkAndUpdateView(view); + expect(update).not.toHaveBeenCalled(); + }); + + }); + + describe('destroy', () => { + it('should destroy component views', () => { + const log: string[] = []; + + class AComp {} + + class ChildProvider { + ngOnDestroy() { log.push('ngOnDestroy'); }; + } + + const {view, rootNodes} = createAndGetRootNodes(compViewDef([ + elementDef( + NodeFlags.None, null, null, 1, 'div', null, null, null, null, + () => compViewDef([ + elementDef(NodeFlags.None, null, null, 1, 'span'), + directiveDef(NodeFlags.OnDestroy, null, 0, ChildProvider, []) + ])), + directiveDef(NodeFlags.IsComponent, null, 0, AComp, [], null, null, ), + ])); + + Services.destroyView(view); + + expect(log).toEqual(['ngOnDestroy']); + }); + + it('should throw on dirty checking destroyed views', () => { + const {view, rootNodes} = createAndGetRootNodes(compViewDef( + [ + elementDef(NodeFlags.None, null, null, 0, 'div'), + ], + (view) => {})); + + Services.destroyView(view); + + expect(() => Services.checkAndUpdateView(view)) + .toThrowError('ViewDestroyedError: Attempt to use a destroyed view: detectChanges'); + }); + }); - expect(log).toEqual(['ngOnDestroy']); }); - - it('should throw on dirty checking destroyed views', () => { - const {view, rootNodes} = createAndGetRootNodes(compViewDef( - [ - elementDef(NodeFlags.None, null, null, 0, 'div'), - ], - (view) => {})); - - Services.destroyView(view); - - expect(() => Services.checkAndUpdateView(view)) - .toThrowError('ViewDestroyedError: Attempt to use a destroyed view: detectChanges'); - }); - }); - - }); } \ No newline at end of file diff --git a/modules/@angular/core/test/view/element_spec.ts b/modules/@angular/core/test/view/element_spec.ts index 1eaeb6e605..dfa7125485 100644 --- a/modules/@angular/core/test/view/element_spec.ts +++ b/modules/@angular/core/test/view/element_spec.ts @@ -8,7 +8,7 @@ import {Injector, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation, WrappedValue, getDebugNode} from '@angular/core'; import {getDebugContext} from '@angular/core/src/errors'; -import {ArgumentType, BindingType, DebugContext, NodeDef, NodeFlags, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, elementDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index'; +import {ArgumentType, BindingType, DebugContext, NodeDef, NodeFlags, OutputType, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, elementDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {ARG_TYPE_VALUES, checkNodeInlineOrDynamic, createRootView, isBrowser, removeNodes} from './helper'; @@ -190,7 +190,8 @@ export function main() { const removeListenerSpy = spyOn(HTMLElement.prototype, 'removeEventListener').and.callThrough(); const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef([elementDef( - NodeFlags.None, null, null, 0, 'button', null, null, ['click'], handleEventSpy)])); + NodeFlags.None, null, null, 0, 'button', null, null, [[null, 'click']], + handleEventSpy)])); rootNodes[0].click(); @@ -256,7 +257,7 @@ export function main() { let preventDefaultSpy: jasmine.Spy; const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef([elementDef( - NodeFlags.None, null, null, 0, 'button', null, null, ['click'], + NodeFlags.None, null, null, 0, 'button', null, null, [[null, 'click']], (view, eventName, event) => { preventDefaultSpy = spyOn(event, 'preventDefault').and.callThrough(); return eventHandlerResult; @@ -281,10 +282,9 @@ export function main() { it('should report debug info on event errors', () => { const addListenerSpy = spyOn(HTMLElement.prototype, 'addEventListener').and.callThrough(); - const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef( - [elementDef(NodeFlags.None, null, null, 0, 'button', null, null, ['click'], () => { - throw new Error('Test'); - })])); + const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef([elementDef( + NodeFlags.None, null, null, 0, 'button', null, null, [[null, 'click']], + () => { throw new Error('Test'); })])); let err: any; try { diff --git a/modules/@angular/core/test/view/ng_content_spec.ts b/modules/@angular/core/test/view/ng_content_spec.ts index e8555f631c..a1322e6ac2 100644 --- a/modules/@angular/core/test/view/ng_content_spec.ts +++ b/modules/@angular/core/test/view/ng_content_spec.ts @@ -30,9 +30,10 @@ export function main() { const aCompViewDef = compViewDef(viewNodes); return [ - elementDef(NodeFlags.None, null, null, 1 + contentNodes.length, 'acomp'), - directiveDef(NodeFlags.None, null, 0, AComp, [], null, null, () => aCompViewDef), - ...contentNodes + elementDef( + NodeFlags.None, null, null, 1 + contentNodes.length, 'acomp', null, null, null, null, + () => aCompViewDef), + directiveDef(NodeFlags.IsComponent, null, 0, AComp, []), ...contentNodes ]; } @@ -110,7 +111,7 @@ export function main() { ])), ]))); - const componentView = asProviderData(view, 1).componentView; + const componentView = asElementData(view, 0).componentView; const view0 = Services.createEmbeddedView(componentView, componentView.def.nodes[1]); attachEmbeddedView(asElementData(componentView, 1), 0, view0); diff --git a/modules/@angular/core/test/view/provider_spec.ts b/modules/@angular/core/test/view/provider_spec.ts index 48b200135c..d396e5fabf 100644 --- a/modules/@angular/core/test/view/provider_spec.ts +++ b/modules/@angular/core/test/view/provider_spec.ts @@ -113,10 +113,10 @@ export function main() { try { createRootView( compViewDef([ - elementDef(NodeFlags.None, null, null, 1, 'div'), - directiveDef( - NodeFlags.None, null, 0, SomeService, [], null, null, - () => compViewDef([textDef(null, ['a'])])) + elementDef( + NodeFlags.None, null, null, 1, 'div', null, null, null, null, + () => compViewDef([textDef(null, ['a'])])), + directiveDef(NodeFlags.IsComponent, null, 0, SomeService, []) ]), TestBed.get(Injector), [], getDOM().createElement('div')); } catch (e) { @@ -174,13 +174,13 @@ export function main() { it('should inject from a parent elment in a parent view', () => { createAndGetRootNodes(compViewDef([ - elementDef(NodeFlags.None, null, null, 1, 'div'), - directiveDef( - NodeFlags.None, null, 0, Dep, [], null, null, + elementDef( + NodeFlags.None, null, null, 1, 'div', null, null, null, null, () => compViewDef([ elementDef(NodeFlags.None, null, null, 1, 'span'), directiveDef(NodeFlags.None, null, 0, SomeService, [Dep]) ])), + directiveDef(NodeFlags.IsComponent, null, 0, Dep, []), ])); expect(instance.dep instanceof Dep).toBeTruthy(); @@ -275,24 +275,24 @@ export function main() { it('should inject ChangeDetectorRef for component providers', () => { const {view, rootNodes} = createAndGetRootNodes(compViewDef([ - elementDef(NodeFlags.None, null, null, 1, 'div'), - directiveDef( - NodeFlags.None, null, 0, SomeService, [ChangeDetectorRef], null, null, + elementDef( + NodeFlags.None, null, null, 1, 'div', null, null, null, null, () => compViewDef([ elementDef(NodeFlags.None, null, null, 0, 'span'), ])), + directiveDef(NodeFlags.IsComponent, null, 0, SomeService, [ChangeDetectorRef]), ])); - const compView = asProviderData(view, 1).componentView; + const compView = asElementData(view, 0).componentView; expect(instance.dep._view).toBe(compView); }); it('should inject RendererV1', () => { createAndGetRootNodes(compViewDef([ - elementDef(NodeFlags.None, null, null, 1, 'span'), - directiveDef( - NodeFlags.None, null, 0, SomeService, [Renderer], null, null, - () => compViewDef([anchorDef(NodeFlags.None, null, null, 0)])) + elementDef( + NodeFlags.None, null, null, 1, 'span', null, null, null, null, + () => compViewDef([anchorDef(NodeFlags.None, null, null, 0)])), + directiveDef(NodeFlags.IsComponent, null, 0, SomeService, [Renderer]) ])); expect(instance.dep.createElement).toBeTruthy(); @@ -300,10 +300,10 @@ export function main() { it('should inject RendererV2', () => { createAndGetRootNodes(compViewDef([ - elementDef(NodeFlags.None, null, null, 1, 'span'), - directiveDef( - NodeFlags.None, null, 0, SomeService, [RendererV2], null, null, - () => compViewDef([anchorDef(NodeFlags.None, null, null, 0)])) + elementDef( + NodeFlags.None, null, null, 1, 'span', null, null, null, null, + () => compViewDef([anchorDef(NodeFlags.None, null, null, 0)])), + directiveDef(NodeFlags.IsComponent, null, 0, SomeService, [RendererV2]) ])); expect(instance.dep.createElement).toBeTruthy(); diff --git a/modules/@angular/core/test/view/query_spec.ts b/modules/@angular/core/test/view/query_spec.ts index f16fe3c8fe..bcfcad9d59 100644 --- a/modules/@angular/core/test/view/query_spec.ts +++ b/modules/@angular/core/test/view/query_spec.ts @@ -50,16 +50,17 @@ export function main() { ]; } - function viewQueryProviders(nodes: NodeDef[]) { + function compViewQueryProviders(extraChildCount: number, nodes: NodeDef[]) { return [ - directiveDef( - NodeFlags.None, null, 0, QueryService, [], null, null, + elementDef( + NodeFlags.None, null, null, 1 + extraChildCount, 'div', null, null, null, null, () => compViewDef([ queryDef( NodeFlags.HasViewQuery | NodeFlags.HasDynamicQuery, someQueryId, {'a': QueryBindingType.All}), ...nodes ])), + directiveDef(NodeFlags.IsComponent, null, 0, QueryService, [], null, null, ), ]; } @@ -110,27 +111,29 @@ export function main() { describe('view queries', () => { it('should query providers in the view', () => { const {view} = createAndGetRootNodes(compViewDef([ - elementDef(NodeFlags.None, null, null, 1, 'div'), - ...viewQueryProviders([ - elementDef(NodeFlags.None, null, null, 1, 'span'), - aServiceProvider(), - ]), + ...compViewQueryProviders( + 0, + [ + elementDef(NodeFlags.None, null, null, 1, 'span'), + aServiceProvider(), + ]), ])); Services.checkAndUpdateView(view); const comp: QueryService = asProviderData(view, 1).instance; - const compView = asProviderData(view, 1).componentView; + const compView = asElementData(view, 0).componentView; expect(comp.a.length).toBe(1); 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, 2, 'div'), - ...viewQueryProviders([ - elementDef(NodeFlags.None, null, null, 0, 'span'), - ]), + ...compViewQueryProviders( + 1, + [ + elementDef(NodeFlags.None, null, null, 0, 'span'), + ]), aServiceProvider(), ])); @@ -225,13 +228,14 @@ export function main() { it('should update view queries if embedded views are added or removed', () => { const {view} = createAndGetRootNodes(compViewDef([ - elementDef(NodeFlags.None, null, null, 1, 'div'), - ...viewQueryProviders([ - anchorDef(NodeFlags.HasEmbeddedViews, null, null, 0, null, embeddedViewDef([ - elementDef(NodeFlags.None, null, null, 1, 'div'), - aServiceProvider(), - ])), - ]), + ...compViewQueryProviders( + 0, + [ + anchorDef(NodeFlags.HasEmbeddedViews, null, null, 0, null, embeddedViewDef([ + elementDef(NodeFlags.None, null, null, 1, 'div'), + aServiceProvider(), + ])), + ]), ])); Services.checkAndUpdateView(view); @@ -239,7 +243,7 @@ export function main() { const comp: QueryService = asProviderData(view, 1).instance; expect(comp.a.length).toBe(0); - const compView = asProviderData(view, 1).componentView; + const compView = asElementData(view, 0).componentView; const childView = Services.createEmbeddedView(compView, compView.def.nodes[1]); attachEmbeddedView(asElementData(compView, 1), 0, childView); Services.checkAndUpdateView(view); diff --git a/modules/@angular/core/test/view/services_spec.ts b/modules/@angular/core/test/view/services_spec.ts index 142518dcf3..53f6f53c95 100644 --- a/modules/@angular/core/test/view/services_spec.ts +++ b/modules/@angular/core/test/view/services_spec.ts @@ -35,20 +35,20 @@ export function main() { function createViewWithData() { const {view} = createAndGetRootNodes(compViewDef([ - elementDef(NodeFlags.None, null, null, 1, 'div'), - directiveDef( - NodeFlags.None, null, 0, AComp, [], null, null, + elementDef( + NodeFlags.None, null, null, 1, 'div', null, null, null, null, () => compViewDef([ elementDef(NodeFlags.None, [['ref', QueryValueType.ElementRef]], null, 2, 'span'), directiveDef(NodeFlags.None, null, 0, AService, []), textDef(null, ['a']) ])), + directiveDef(NodeFlags.IsComponent, null, 0, AComp, []), ])); return view; } it('should provide data for elements', () => { const view = createViewWithData(); - const compView = asProviderData(view, 1).componentView; + const compView = asElementData(view, 0).componentView; const debugCtx = Services.createDebugContext(compView, 0); @@ -65,7 +65,7 @@ export function main() { it('should provide data for text nodes', () => { const view = createViewWithData(); - const compView = asProviderData(view, 1).componentView; + const compView = asElementData(view, 0).componentView; const debugCtx = Services.createDebugContext(compView, 2); @@ -79,7 +79,7 @@ export function main() { it('should provide data for other nodes based on the nearest element parent', () => { const view = createViewWithData(); - const compView = asProviderData(view, 1).componentView; + const compView = asElementData(view, 0).componentView; const debugCtx = Services.createDebugContext(compView, 1); diff --git a/modules/benchmarks/src/tree/ng2_next/tree.ts b/modules/benchmarks/src/tree/ng2_next/tree.ts index 90df93ea2a..6ffb379b4c 100644 --- a/modules/benchmarks/src/tree/ng2_next/tree.ts +++ b/modules/benchmarks/src/tree/ng2_next/tree.ts @@ -26,8 +26,8 @@ let viewFlags = ViewFlags.None; function TreeComponent_Host(): ViewDefinition { return viewDef(viewFlags, [ - elementDef(NodeFlags.None, null, null, 1, 'tree'), - directiveDef(NodeFlags.None, null, 0, TreeComponent, [], null, null, TreeComponent_0), + elementDef(NodeFlags.None, null, null, 1, 'tree', null, null, null, null, TreeComponent_0), + directiveDef(NodeFlags.IsComponent, null, 0, TreeComponent, []), ]); } @@ -35,9 +35,8 @@ 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), + elementDef(NodeFlags.None, null, null, 1, 'tree', null, null, null, null, TreeComponent_0), + directiveDef(NodeFlags.IsComponent, null, 0, TreeComponent, [], {data: [0, 'data']}), ], (check, view) => { const cmp = view.component; @@ -49,9 +48,8 @@ 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), + elementDef(NodeFlags.None, null, null, 1, 'tree', null, null, null, null, TreeComponent_0), + directiveDef(NodeFlags.IsComponent, null, 0, TreeComponent, [], {data: [0, 'data']}), ], (check, view) => { const cmp = view.component;