fix(core): host bindings and host listeners for animations

Host bindings / listeners for animation properties should use
the renderer of the component view.
This commit is contained in:
Tobias Bosch 2017-02-21 13:56:56 -08:00 committed by Igor Minar
parent 6b7937f112
commit 5049a50bf6
20 changed files with 635 additions and 603 deletions

View File

@ -282,7 +282,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
elName = null; elName = null;
} }
let {flags, usedEvents, queryMatchesExpr, hostBindings, hostEvents} = const {flags, usedEvents, queryMatchesExpr, hostBindings, hostEvents} =
this._visitElementOrTemplate(nodeIndex, ast); this._visitElementOrTemplate(nodeIndex, ast);
let inputDefs: o.Expression[] = []; 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: // Note: inputDefs have to be in the same order as hostBindings:
// - first the entries from the directives, then the ones from the element. // - first the entries from the directives, then the ones from the element.
ast.directives.forEach( ast.directives.forEach(
(dirAst, dirIndex) => inputDefs.push(...elementBindingDefs(dirAst.hostProperties))); (dirAst, dirIndex) =>
inputDefs.push(...elementBindingDefs(ast.inputs)); inputDefs.push(...elementBindingDefs(dirAst.hostProperties, dirAst)));
inputDefs.push(...elementBindingDefs(ast.inputs, null));
outputDefs = usedEvents.map(([target, eventName]) => { outputDefs = usedEvents.map(
return target ? o.literalArr([o.literal(target), o.literal(eventName)]) : ([target, eventName]) => o.literalArr([o.literal(target), o.literal(eventName)]));
o.literal(eventName);
});
} }
templateVisitAll(this, ast.children); templateVisitAll(this, ast.children);
const childCount = this.nodeDefs.length - nodeIndex - 1; 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( // elementDef(
// flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number, // flags: NodeFlags, matchedQueriesDsl: [string | number, QueryValueType][],
// childCount: number, name: string, fixedAttrs: {[name: string]: string} = {}, // ngContentIndex: number, childCount: number, namespaceAndName: string,
// fixedAttrs: [string, string][] = [],
// bindings?: // bindings?:
// ([BindingType.ElementClass, string] | [BindingType.ElementStyle, string, string] | // ([BindingType.ElementClass, string] | [BindingType.ElementStyle, string, string] |
// [BindingType.ElementAttribute | BindingType.ElementProperty, string, // [BindingType.ElementAttribute | BindingType.ElementProperty |
// SecurityContext])[], // BindingType.DirectiveHostProperty, string, SecurityContext])[],
// outputs?: (string | [string, string])[], eventHandlerFn: ElementHandleEventFn): NodeDef; // outputs?: ([OutputType.ElementOutput | OutputType.DirectiveHostOutput, string, string])[],
// handleEvent?: ElementHandleEventFn,
// componentView?: () => ViewDefinition, componentRendererType?: RendererTypeV2): NodeDef;
const nodeDef = () => o.importExpr(createIdentifier(Identifiers.elementDef)).callFn([ const nodeDef = () => o.importExpr(createIdentifier(Identifiers.elementDef)).callFn([
o.literal(flags), queryMatchesExpr, o.literal(ast.ngContentIndex), o.literal(childCount), o.literal(flags), queryMatchesExpr, o.literal(ast.ngContentIndex), o.literal(childCount),
o.literal(elName), elName ? fixedAttrsDef(ast) : o.NULL_EXPR, o.literal(elName), elName ? fixedAttrsDef(ast) : o.NULL_EXPR,
inputDefs.length ? o.literalArr(inputDefs) : o.NULL_EXPR, inputDefs.length ? o.literalArr(inputDefs) : o.NULL_EXPR,
outputDefs.length ? o.literalArr(outputDefs) : 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; this.nodeDefs[nodeIndex] = nodeDef;
@ -336,11 +346,11 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
references: ReferenceAst[], references: ReferenceAst[],
queryMatches: QueryMatch[] queryMatches: QueryMatch[]
}): { }): {
flags: number, flags: NodeFlags,
usedEvents: [string, string][], usedEvents: [string, string][],
queryMatchesExpr: o.Expression, queryMatchesExpr: o.Expression,
hostBindings: {value: AST, context: 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; let flags = NodeFlags.None;
if (ast.hasViewContainer) { if (ast.hasViewContainer) {
@ -348,17 +358,17 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
} }
const usedEvents = new Map<string, [string, string]>(); const usedEvents = new Map<string, [string, string]>();
ast.outputs.forEach((event) => { ast.outputs.forEach((event) => {
const en = eventName(event); const {name, target} = elementEventNameAndTarget(event, null);
usedEvents.set(elementEventFullName(event.target, en), [event.target, en]); usedEvents.set(elementEventFullName(target, name), [target, name]);
}); });
ast.directives.forEach((dirAst) => { ast.directives.forEach((dirAst) => {
dirAst.hostEvents.forEach((event) => { dirAst.hostEvents.forEach((event) => {
const en = eventName(event); const {name, target} = elementEventNameAndTarget(event, dirAst);
usedEvents.set(elementEventFullName(event.target, en), [event.target, en]); usedEvents.set(elementEventFullName(target, name), [target, name]);
}); });
}); });
const hostBindings: {value: AST, context: o.Expression}[] = []; 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); const componentFactoryResolverProvider = createComponentFactoryResolver(ast.directives);
if (componentFactoryResolverProvider) { if (componentFactoryResolverProvider) {
this._visitProvider(componentFactoryResolverProvider, ast.queryMatches); this._visitProvider(componentFactoryResolverProvider, ast.queryMatches);
@ -386,7 +396,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
let queryMatchExprs: o.Expression[] = []; let queryMatchExprs: o.Expression[] = [];
ast.queryMatches.forEach((match) => { ast.queryMatches.forEach((match) => {
let valueType: number; let valueType: QueryValueType;
if (tokenReference(match.value) === resolveIdentifier(Identifiers.ElementRef)) { if (tokenReference(match.value) === resolveIdentifier(Identifiers.ElementRef)) {
valueType = QueryValueType.ElementRef; valueType = QueryValueType.ElementRef;
} else if (tokenReference(match.value) === resolveIdentifier(Identifiers.ViewContainerRef)) { } else if (tokenReference(match.value) === resolveIdentifier(Identifiers.ViewContainerRef)) {
@ -399,7 +409,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
} }
}); });
ast.references.forEach((ref) => { ast.references.forEach((ref) => {
let valueType: number; let valueType: QueryValueType;
if (!ref.value) { if (!ref.value) {
valueType = QueryValueType.RenderElement; valueType = QueryValueType.RenderElement;
} else if (tokenReference(ref.value) === resolveIdentifier(Identifiers.TemplateRef)) { } 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)])); queryMatchExprs.push(o.literalArr([o.literal(ref.name), o.literal(valueType)]));
} }
}); });
ast.outputs.forEach( ast.outputs.forEach((outputAst) => {
(outputAst) => { hostEvents.push({context: COMP_VAR, eventAst: outputAst}); }); hostEvents.push({context: COMP_VAR, eventAst: outputAst, dirAst: null});
});
return { return {
flags, flags,
@ -423,19 +434,19 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
} }
private _visitDirective( private _visitDirective(
providerAst: ProviderAst, directiveAst: DirectiveAst, directiveIndex: number, providerAst: ProviderAst, dirAst: DirectiveAst, directiveIndex: number,
elementNodeIndex: number, refs: ReferenceAst[], queryMatches: QueryMatch[], elementNodeIndex: number, refs: ReferenceAst[], queryMatches: QueryMatch[],
usedEvents: Map<string, any>, queryIds: StaticAndDynamicQueryIds): { usedEvents: Map<string, any>, queryIds: StaticAndDynamicQueryIds): {
hostBindings: {value: AST, context: o.Expression}[], 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; const nodeIndex = this.nodeDefs.length;
// reserve the space in the nodeDefs array so we can add children // reserve the space in the nodeDefs array so we can add children
this.nodeDefs.push(null); this.nodeDefs.push(null);
directiveAst.directive.queries.forEach((query, queryIndex) => { dirAst.directive.queries.forEach((query, queryIndex) => {
let flags = NodeFlags.HasContentQuery; let flags = NodeFlags.HasContentQuery;
const queryId = directiveAst.contentQueryStartId + queryIndex; const queryId = dirAst.contentQueryStartId + queryIndex;
if (queryIds.staticQueryIds.has(queryId)) { if (queryIds.staticQueryIds.has(queryId)) {
flags |= NodeFlags.HasStaticQuery; flags |= NodeFlags.HasStaticQuery;
} else { } else {
@ -454,7 +465,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
// I.e. we only allow queries as children of directives nodes. // I.e. we only allow queries as children of directives nodes.
const childCount = this.nodeDefs.length - nodeIndex - 1; const childCount = this.nodeDefs.length - nodeIndex - 1;
const {flags, queryMatchExprs, providerExpr, providerType, depsExpr} = let {flags, queryMatchExprs, providerExpr, providerType, depsExpr} =
this._visitProviderOrDirective(providerAst, queryMatches); this._visitProviderOrDirective(providerAst, queryMatches);
refs.forEach((ref) => { refs.forEach((ref) => {
@ -465,21 +476,18 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
} }
}); });
let rendererType = o.NULL_EXPR; if (dirAst.directive.isComponent) {
let compView = o.NULL_EXPR; flags |= NodeFlags.IsComponent;
if (directiveAst.directive.isComponent) {
compView = o.importExpr({reference: directiveAst.directive.componentViewType});
rendererType = o.importExpr({reference: directiveAst.directive.rendererType});
} }
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)]); 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! // 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); return new o.LiteralMapEntry(inputAst.directiveName, mapValue, false);
}); });
const outputDefs: o.LiteralMapEntry[] = []; const outputDefs: o.LiteralMapEntry[] = [];
const dirMeta = directiveAst.directive; const dirMeta = dirAst.directive;
Object.keys(dirMeta.outputs).forEach((propName) => { Object.keys(dirMeta.outputs).forEach((propName) => {
const eventName = dirMeta.outputs[propName]; const eventName = dirMeta.outputs[propName];
if (usedEvents.has(eventName)) { if (usedEvents.has(eventName)) {
@ -487,24 +495,24 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
outputDefs.push(new o.LiteralMapEntry(propName, o.literal(eventName), false)); 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( this._addUpdateExpressions(
nodeIndex, 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); this.updateDirectivesExpressions);
} }
const dirContextExpr = o.importExpr(createIdentifier(Identifiers.nodeValue)).callFn([ const dirContextExpr = o.importExpr(createIdentifier(Identifiers.nodeValue)).callFn([
VIEW_VAR, o.literal(nodeIndex) VIEW_VAR, o.literal(nodeIndex)
]); ]);
const hostBindings = directiveAst.hostProperties.map((hostBindingAst) => { const hostBindings = dirAst.hostProperties.map((hostBindingAst) => {
return { return {
value: (<ASTWithSource>hostBindingAst.value).ast, value: (<ASTWithSource>hostBindingAst.value).ast,
context: dirContextExpr, context: dirContextExpr,
}; };
}); });
const hostEvents = directiveAst.hostEvents.map( const hostEvents = dirAst.hostEvents.map(
(hostEventAst) => { return {context: dirContextExpr, eventAst: hostEventAst}; }); (hostEventAst) => { return {context: dirContextExpr, eventAst: hostEventAst, dirAst}; });
// directiveDef( // directiveDef(
@ -516,7 +524,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
o.literal(flags), queryMatchExprs.length ? o.literalArr(queryMatchExprs) : o.NULL_EXPR, o.literal(flags), queryMatchExprs.length ? o.literalArr(queryMatchExprs) : o.NULL_EXPR,
o.literal(childCount), providerExpr, depsExpr, o.literal(childCount), providerExpr, depsExpr,
inputDefs.length ? new o.LiteralMapExpr(inputDefs) : o.NULL_EXPR, 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; this.nodeDefs[nodeIndex] = nodeDef;
@ -543,10 +551,10 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
} }
private _visitProviderOrDirective(providerAst: ProviderAst, queryMatches: QueryMatch[]): { private _visitProviderOrDirective(providerAst: ProviderAst, queryMatches: QueryMatch[]): {
flags: number, flags: NodeFlags,
queryMatchExprs: o.Expression[], queryMatchExprs: o.Expression[],
providerExpr: o.Expression, providerExpr: o.Expression,
providerType: number, providerType: ProviderType,
depsExpr: o.Expression depsExpr: o.Expression
} { } {
let flags = NodeFlags.None; let flags = NodeFlags.None;
@ -702,10 +710,11 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
} }
private _createElementHandleEventFn( private _createElementHandleEventFn(
nodeIndex: number, handlers: {context: o.Expression, eventAst: BoundEventAst}[]) { nodeIndex: number,
handlers: {context: o.Expression, eventAst: BoundEventAst, dirAst: DirectiveAst}[]) {
const handleEventStmts: o.Statement[] = []; const handleEventStmts: o.Statement[] = [];
let handleEventBindingCount = 0; let handleEventBindingCount = 0;
handlers.forEach(({context, eventAst}) => { handlers.forEach(({context, eventAst, dirAst}) => {
const bindingId = `${handleEventBindingCount++}`; const bindingId = `${handleEventBindingCount++}`;
const nameResolver = context === COMP_VAR ? this : null; const nameResolver = context === COMP_VAR ? this : null;
const expression = const expression =
@ -716,7 +725,8 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
if (allowDefault) { if (allowDefault) {
trueStmts.push(ALLOW_DEFAULT_VAR.set(allowDefault.and(ALLOW_DEFAULT_VAR)).toStmt()); 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( handleEventStmts.push(
new o.IfStmt(o.literal(fullEventName).identical(EVENT_NAME_VAR), trueStmts)); 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): 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) : return providerAst.multiProvider ? multiProviderDef(providerAst.providers) :
singleProviderDef(providerAst.providers[0]); singleProviderDef(providerAst.providers[0]);
} }
function multiProviderDef(providers: CompileProviderMetadata[]): 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 allDepDefs: o.Expression[] = [];
const allParams: o.FnParam[] = []; const allParams: o.FnParam[] = [];
const exprs = providers.map((provider, providerIndex) => { const exprs = providers.map((provider, providerIndex) => {
@ -791,9 +801,9 @@ function multiProviderDef(providers: CompileProviderMetadata[]):
} }
function singleProviderDef(providerMeta: 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 providerExpr: o.Expression;
let providerType: number; let providerType: ProviderType;
let deps: CompileDiDependencyMetadata[]; let deps: CompileDiDependencyMetadata[];
if (providerMeta.useClass) { if (providerMeta.useClass) {
providerExpr = o.importExpr(providerMeta.useClass); providerExpr = o.importExpr(providerMeta.useClass);
@ -852,7 +862,7 @@ function needsAdditionalRootNode(ast: TemplateAst): boolean {
return ast instanceof NgContentAst; return ast instanceof NgContentAst;
} }
function lifecycleHookToNodeFlag(lifecycleHook: LifecycleHooks): number { function lifecycleHookToNodeFlag(lifecycleHook: LifecycleHooks): NodeFlags {
let nodeFlag = NodeFlags.None; let nodeFlag = NodeFlags.None;
switch (lifecycleHook) { switch (lifecycleHook) {
case LifecycleHooks.AfterContentChecked: case LifecycleHooks.AfterContentChecked:
@ -883,7 +893,8 @@ function lifecycleHookToNodeFlag(lifecycleHook: LifecycleHooks): number {
return nodeFlag; return nodeFlag;
} }
function elementBindingDefs(inputAsts: BoundElementPropertyAst[]): o.Expression[] { function elementBindingDefs(
inputAsts: BoundElementPropertyAst[], dirAst: DirectiveAst): o.Expression[] {
return inputAsts.map((inputAst) => { return inputAsts.map((inputAst) => {
switch (inputAst.type) { switch (inputAst.type) {
case PropertyBindingType.Attribute: case PropertyBindingType.Attribute:
@ -897,8 +908,11 @@ function elementBindingDefs(inputAsts: BoundElementPropertyAst[]): o.Expression[
o.literal(inputAst.securityContext) o.literal(inputAst.securityContext)
]); ]);
case PropertyBindingType.Animation: case PropertyBindingType.Animation:
const bindingType = dirAst && dirAst.directive.isComponent ?
BindingType.ComponentHostProperty :
BindingType.ElementProperty;
return o.literalArr([ return o.literalArr([
o.literal(BindingType.ElementProperty), o.literal('@' + inputAst.name), o.literal(bindingType), o.literal('@' + inputAst.name),
o.literal(inputAst.securityContext) o.literal(inputAst.securityContext)
]); ]);
case PropertyBindingType.Class: case PropertyBindingType.Class:
@ -1025,6 +1039,14 @@ function createComponentFactoryResolver(directives: DirectiveAst[]): ProviderAst
return null; return null;
} }
function eventName(eventAst: BoundEventAst): string { function elementEventNameAndTarget(
return eventAst.isAnimation ? `@${eventAst.name}.${eventAst.phase}` : eventAst.name; 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;
}
} }

View File

@ -7,9 +7,10 @@
*/ */
import {isDevMode} from '../application_ref'; import {isDevMode} from '../application_ref';
import {RendererTypeV2, RendererV2} from '../render/api';
import {SecurityContext} from '../security'; 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'; import {checkAndUpdateBinding, dispatchEvent, elementEventFullName, filterQueryId, getParentRenderElement, resolveViewDefinition, sliceErrorStack, splitMatchedQueriesDsl, splitNamespace} from './util';
const NOOP: any = () => {}; const NOOP: any = () => {};
@ -34,20 +35,20 @@ export function anchorDef(
parent: undefined, parent: undefined,
renderParent: undefined, renderParent: undefined,
bindingIndex: undefined, bindingIndex: undefined,
disposableIndex: undefined, outputIndex: undefined,
// regular values // regular values
flags, flags,
childFlags: 0, childFlags: 0,
childMatchedQueries: 0, matchedQueries, matchedQueryIds, references, ngContentIndex, childCount, childMatchedQueries: 0, matchedQueries, matchedQueryIds, references, ngContentIndex, childCount,
bindings: [], bindings: [],
disposableCount: 0, outputs: [],
element: { element: {
ns: undefined, ns: undefined,
name: undefined, name: undefined,
attrs: undefined, attrs: undefined, template, source,
outputs: [], template, source, componentProvider: undefined,
// will bet set by the view definition componentView: undefined,
component: undefined, componentRendererType: undefined,
publicProviders: undefined, publicProviders: undefined,
allProviders: undefined, handleEvent allProviders: undefined, handleEvent
}, },
@ -65,8 +66,13 @@ export function elementDef(
fixedAttrs: [string, string][] = [], fixedAttrs: [string, string][] = [],
bindings?: bindings?:
([BindingType.ElementClass, string] | [BindingType.ElementStyle, string, string] | ([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) { if (!handleEvent) {
handleEvent = NOOP; handleEvent = NOOP;
} }
@ -93,29 +99,35 @@ export function elementDef(
break; break;
case BindingType.ElementAttribute: case BindingType.ElementAttribute:
case BindingType.ElementProperty: case BindingType.ElementProperty:
case BindingType.ComponentHostProperty:
securityContext = <SecurityContext>entry[2]; securityContext = <SecurityContext>entry[2];
break; break;
} }
bindingDefs[i] = {type: bindingType, ns, name, nonMinifiedName: name, securityContext, suffix}; bindingDefs[i] = {type: bindingType, ns, name, nonMinifiedName: name, securityContext, suffix};
} }
outputs = outputs || []; outputs = outputs || [];
const outputDefs: ElementOutputDef[] = new Array(outputs.length); const outputDefs: OutputDef[] = new Array(outputs.length);
for (let i = 0; i < outputs.length; i++) { for (let i = 0; i < outputs.length; i++) {
const output = outputs[i]; const [target, eventName] = outputs[i];
let target: string; outputDefs[i] = {
let eventName: string; type: OutputType.ElementOutput,
if (Array.isArray(output)) { target: <any>target, eventName,
[target, eventName] = output; propName: undefined
} else { };
eventName = output;
}
outputDefs[i] = {eventName: eventName, target: target};
} }
fixedAttrs = fixedAttrs || []; fixedAttrs = fixedAttrs || [];
const attrs = <[string, string, string][]>fixedAttrs.map(([namespaceAndName, value]) => { const attrs = <[string, string, string][]>fixedAttrs.map(([namespaceAndName, value]) => {
const [ns, name] = splitNamespace(namespaceAndName); const [ns, name] = splitNamespace(namespaceAndName);
return [ns, name, value]; 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 { return {
type: NodeType.Element, type: NodeType.Element,
// will bet set by the view definition // will bet set by the view definition
@ -124,21 +136,21 @@ export function elementDef(
parent: undefined, parent: undefined,
renderParent: undefined, renderParent: undefined,
bindingIndex: undefined, bindingIndex: undefined,
disposableIndex: undefined, outputIndex: undefined,
// regular values // regular values
flags, flags,
childFlags: 0, childFlags: 0,
childMatchedQueries: 0, matchedQueries, matchedQueryIds, references, ngContentIndex, childCount, childMatchedQueries: 0, matchedQueries, matchedQueryIds, references, ngContentIndex, childCount,
bindings: bindingDefs, bindings: bindingDefs,
disposableCount: outputDefs.length, outputs: outputDefs,
element: { element: {
ns, ns,
name, name,
attrs, attrs,
outputs: outputDefs, source, source,
template: undefined, template: undefined,
// will bet set by the view definition // will bet set by the view definition
component: undefined, componentProvider: undefined, componentView, componentRendererType,
publicProviders: undefined, publicProviders: undefined,
allProviders: undefined, handleEvent, allProviders: undefined, handleEvent,
}, },
@ -174,21 +186,24 @@ export function createElement(view: ViewData, renderHost: any, def: NodeDef): El
renderer.setAttribute(el, name, value, ns); renderer.setAttribute(el, name, value, ns);
} }
} }
if (elDef.outputs.length) { return el;
for (let i = 0; i < elDef.outputs.length; i++) { }
const output = elDef.outputs[i];
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( const handleEventClosure = renderEventHandlerClosure(
view, def.index, elementEventFullName(output.target, output.eventName)); 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 = const disposable =
<any>renderer.listen(output.target || el, output.eventName, handleEventClosure); <any>listenerView.renderer.listen(listenTarget || el, output.eventName, handleEventClosure);
view.disposables[def.disposableIndex + i] = disposable; 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) { function renderEventHandlerClosure(view: ViewData, index: number, eventName: string) {
@ -223,7 +238,8 @@ function checkAndUpdateElementValue(view: ViewData, def: NodeDef, bindingIdx: nu
return; return;
} }
const binding = def.bindings[bindingIdx]; 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; const name = binding.name;
switch (binding.type) { switch (binding.type) {
case BindingType.ElementAttribute: case BindingType.ElementAttribute:
@ -238,6 +254,9 @@ function checkAndUpdateElementValue(view: ViewData, def: NodeDef, bindingIdx: nu
case BindingType.ElementProperty: case BindingType.ElementProperty:
setElementProperty(view, binding, renderNode, name, value); setElementProperty(view, binding, renderNode, name, value);
break; break;
case BindingType.ComponentHostProperty:
setElementProperty(elData.componentView, binding, renderNode, name, value);
break;
} }
} }

View File

@ -18,7 +18,7 @@ export function ngContentDef(ngContentIndex: number, index: number): NodeDef {
parent: undefined, parent: undefined,
renderParent: undefined, renderParent: undefined,
bindingIndex: undefined, bindingIndex: undefined,
disposableIndex: undefined, outputIndex: undefined,
// regular values // regular values
flags: 0, flags: 0,
childFlags: 0, childFlags: 0,
@ -28,7 +28,7 @@ export function ngContentDef(ngContentIndex: number, index: number): NodeDef {
references: {}, ngContentIndex, references: {}, ngContentIndex,
childCount: 0, childCount: 0,
bindings: [], bindings: [],
disposableCount: 0, outputs: [],
element: undefined, element: undefined,
provider: undefined, provider: undefined,
text: undefined, text: undefined,

View File

@ -15,7 +15,7 @@ import {ViewEncapsulation} from '../metadata/view';
import {Renderer as RendererV1, RendererFactoryV2, RendererTypeV2, RendererV2} from '../render/api'; import {Renderer as RendererV1, RendererFactoryV2, RendererTypeV2, RendererV2} from '../render/api';
import {createChangeDetectorRef, createInjector, createRendererV1, 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 {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'; import {checkAndUpdateBinding, dispatchEvent, filterQueryId, isComponentView, splitMatchedQueriesDsl, tokenKey, viewParentEl} from './util';
const RendererV1TokenKey = tokenKey(RendererV1); const RendererV1TokenKey = tokenKey(RendererV1);
@ -31,8 +31,7 @@ const NOT_CREATED = new Object();
export function directiveDef( export function directiveDef(
flags: NodeFlags, matchedQueries: [string | number, QueryValueType][], childCount: number, flags: NodeFlags, matchedQueries: [string | number, QueryValueType][], childCount: number,
ctor: any, deps: ([DepFlags, any] | any)[], props?: {[name: string]: [number, string]}, ctor: any, deps: ([DepFlags, any] | any)[], props?: {[name: string]: [number, string]},
outputs?: {[name: string]: string}, component?: () => ViewDefinition, outputs?: {[name: string]: string}): NodeDef {
rendererType?: RendererTypeV2): NodeDef {
const bindings: BindingDef[] = []; const bindings: BindingDef[] = [];
if (props) { if (props) {
for (let prop in props) { for (let prop in props) {
@ -46,15 +45,16 @@ export function directiveDef(
}; };
} }
} }
const outputDefs: DirectiveOutputDef[] = []; const outputDefs: OutputDef[] = [];
if (outputs) { if (outputs) {
for (let propName in 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( return _def(
NodeType.Directive, flags, matchedQueries, childCount, ProviderType.Class, ctor, ctor, deps, 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 { export function pipeDef(flags: NodeFlags, ctor: any, deps: ([DepFlags, any] | any)[]): NodeDef {
@ -70,14 +70,8 @@ export function providerDef(
export function _def( export function _def(
type: NodeType, flags: NodeFlags, matchedQueriesDsl: [string | number, QueryValueType][], type: NodeType, flags: NodeFlags, matchedQueriesDsl: [string | number, QueryValueType][],
childCount: number, providerType: ProviderType, token: any, value: any, childCount: number, providerType: ProviderType, token: any, value: any,
deps: ([DepFlags, any] | any)[], bindings?: BindingDef[], outputs?: DirectiveOutputDef[], deps: ([DepFlags, any] | any)[], bindings?: BindingDef[], outputs?: OutputDef[]): NodeDef {
component?: () => ViewDefinition, rendererType?: RendererTypeV2): NodeDef {
const {matchedQueries, references, matchedQueryIds} = splitMatchedQueriesDsl(matchedQueriesDsl); 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) { if (!outputs) {
outputs = []; outputs = [];
} }
@ -96,9 +90,6 @@ export function _def(
} }
return {flags, token, tokenKey: tokenKey(token)}; return {flags, token, tokenKey: tokenKey(token)};
}); });
if (component) {
flags = flags | NodeFlags.HasComponent;
}
return { return {
type, type,
@ -108,20 +99,14 @@ export function _def(
parent: undefined, parent: undefined,
renderParent: undefined, renderParent: undefined,
bindingIndex: undefined, bindingIndex: undefined,
disposableIndex: undefined, outputIndex: undefined,
// regular values // regular values
flags, flags,
childFlags: 0, childFlags: 0,
childMatchedQueries: 0, matchedQueries, matchedQueryIds, references, childMatchedQueries: 0, matchedQueries, matchedQueryIds, references,
ngContentIndex: undefined, childCount, bindings, ngContentIndex: undefined, childCount, bindings, outputs,
disposableCount: outputs.length,
element: undefined, element: undefined,
provider: { provider: {type: providerType, token, tokenKey: tokenKey(token), value, deps: depDefs},
type: providerType,
token,
tokenKey: tokenKey(token), value,
deps: depDefs, outputs, component, rendererType
},
text: undefined, text: undefined,
pureExpression: undefined, pureExpression: undefined,
query: undefined, query: undefined,
@ -149,17 +134,17 @@ export function createPipeInstance(view: ViewData, def: NodeDef): any {
export function createDirectiveInstance(view: ViewData, def: NodeDef): any { export function createDirectiveInstance(view: ViewData, def: NodeDef): any {
// components can see other private services, other directives can't. // 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; const providerDef = def.provider;
// directives are always eager and classes! // directives are always eager and classes!
const instance = const instance =
createClass(view, def.parent, allowPrivateServices, def.provider.value, def.provider.deps); createClass(view, def.parent, allowPrivateServices, def.provider.value, def.provider.deps);
if (providerDef.outputs.length) { if (def.outputs.length) {
for (let i = 0; i < providerDef.outputs.length; i++) { for (let i = 0; i < def.outputs.length; i++) {
const output = providerDef.outputs[i]; const output = def.outputs[i];
const subscription = instance[output.propName].subscribe( const subscription = instance[output.propName].subscribe(
eventHandlerClosure(view, def.parent.index, output.eventName)); 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; return instance;
@ -371,7 +356,7 @@ export function resolveDep(
function findCompView(view: ViewData, elDef: NodeDef, allowPrivateServices: boolean) { function findCompView(view: ViewData, elDef: NodeDef, allowPrivateServices: boolean) {
let compView: ViewData; let compView: ViewData;
if (allowPrivateServices) { if (allowPrivateServices) {
compView = asProviderData(view, elDef.element.component.index).componentView; compView = asElementData(view, elDef.index).componentView;
} else { } else {
compView = view; compView = view;
while (compView.parent && !isComponentView(compView)) { while (compView.parent && !isComponentView(compView)) {
@ -396,8 +381,8 @@ function checkAndUpdateProp(
changed = checkAndUpdateBinding(view, def, bindingIdx, value); changed = checkAndUpdateBinding(view, def, bindingIdx, value);
} }
if (changed) { if (changed) {
if (def.flags & NodeFlags.HasComponent) { if (def.flags & NodeFlags.IsComponent) {
const compView = providerData.componentView; const compView = asElementData(view, def.parent.index).componentView;
if (compView.def.flags & ViewFlags.OnPush) { if (compView.def.flags & ViewFlags.OnPush) {
compView.state |= ViewState.ChecksEnabled; compView.state |= ViewState.ChecksEnabled;
} }

View File

@ -43,7 +43,7 @@ function _pureExpressionDef(type: PureExpressionType, propertyNames: string[]):
parent: undefined, parent: undefined,
renderParent: undefined, renderParent: undefined,
bindingIndex: undefined, bindingIndex: undefined,
disposableIndex: undefined, outputIndex: undefined,
// regular values // regular values
flags: 0, flags: 0,
childFlags: 0, childFlags: 0,
@ -53,7 +53,7 @@ function _pureExpressionDef(type: PureExpressionType, propertyNames: string[]):
references: {}, references: {},
ngContentIndex: undefined, ngContentIndex: undefined,
childCount: 0, bindings, childCount: 0, bindings,
disposableCount: 0, outputs: [],
element: undefined, element: undefined,
provider: undefined, provider: undefined,
text: undefined, text: undefined,

View File

@ -31,7 +31,7 @@ export function queryDef(
parent: undefined, parent: undefined,
renderParent: undefined, renderParent: undefined,
bindingIndex: undefined, bindingIndex: undefined,
disposableIndex: undefined, outputIndex: undefined,
// regular values // regular values
flags, flags,
childFlags: 0, childFlags: 0,
@ -42,7 +42,7 @@ export function queryDef(
references: {}, references: {},
childCount: 0, childCount: 0,
bindings: [], bindings: [],
disposableCount: 0, outputs: [],
element: undefined, element: undefined,
provider: undefined, provider: undefined,
text: undefined, text: undefined,

View File

@ -42,7 +42,7 @@ class ComponentFactory_ extends ComponentFactory<any> {
injector: Injector, projectableNodes: any[][] = null, injector: Injector, projectableNodes: any[][] = null,
rootSelectorOrNode: string|any = null): ComponentRef<any> { rootSelectorOrNode: string|any = null): ComponentRef<any> {
const viewDef = resolveViewDefinition(this._viewClass); 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( const view = Services.createRootView(
injector, projectableNodes || [], rootSelectorOrNode, viewDef, EMPTY_CONTEXT); injector, projectableNodes || [], rootSelectorOrNode, viewDef, EMPTY_CONTEXT);
const component = asProviderData(view, componentNodeIndex).instance; const component = asProviderData(view, componentNodeIndex).instance;
@ -255,7 +255,7 @@ export function createInjector(view: ViewData, elDef: NodeDef): Injector {
class Injector_ implements Injector { class Injector_ implements Injector {
constructor(private view: ViewData, private elDef: NodeDef) {} constructor(private view: ViewData, private elDef: NodeDef) {}
get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND): any { 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( return Services.resolveDep(
this.view, this.elDef, allowPrivateServices, this.view, this.elDef, allowPrivateServices,
{flags: DepFlags.None, token, tokenKey: tokenKey(token)}, notFoundValue); {flags: DepFlags.None, token, tokenKey: tokenKey(token)}, notFoundValue);

View File

@ -205,6 +205,7 @@ function debugCheckFn(
const binding = nodeDef.bindings[i]; const binding = nodeDef.bindings[i];
const value = values[i]; const value = values[i];
if ((binding.type === BindingType.ElementProperty || if ((binding.type === BindingType.ElementProperty ||
binding.type === BindingType.ComponentHostProperty ||
binding.type === BindingType.DirectiveProperty) && binding.type === BindingType.DirectiveProperty) &&
checkBinding(view, nodeDef, i, value)) { checkBinding(view, nodeDef, i, value)) {
bindingValues[normalizeDebugBindingName(binding.nonMinifiedName)] = bindingValues[normalizeDebugBindingName(binding.nonMinifiedName)] =
@ -273,7 +274,6 @@ class DebugContext_ implements DebugContext {
private nodeDef: NodeDef; private nodeDef: NodeDef;
private elView: ViewData; private elView: ViewData;
private elDef: NodeDef; private elDef: NodeDef;
private compProviderDef: NodeDef;
constructor(public view: ViewData, public nodeIndex: number) { constructor(public view: ViewData, public nodeIndex: number) {
if (nodeIndex == null) { if (nodeIndex == null) {
this.nodeIndex = nodeIndex = 0; this.nodeIndex = nodeIndex = 0;
@ -292,21 +292,14 @@ class DebugContext_ implements DebugContext {
} }
this.elDef = elDef; this.elDef = elDef;
this.elView = elView; 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 injector(): Injector { return createInjector(this.elView, this.elDef); }
get component(): any { get component(): any { return this.elOrCompView.component; }
if (this.compProviderDef) { get context(): any { return this.elOrCompView.context; }
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 providerTokens(): any[] { get providerTokens(): any[] {
const tokens: any[] = []; const tokens: any[] = [];
if (this.elDef) { if (this.elDef) {
@ -343,10 +336,7 @@ class DebugContext_ implements DebugContext {
} }
} }
get componentRenderElement() { get componentRenderElement() {
const view = this.compProviderDef ? const elData = findHostElement(this.elOrCompView);
asProviderData(this.elView, this.compProviderDef.index).componentView :
this.view;
const elData = findHostElement(view);
return elData ? elData.renderElement : undefined; return elData ? elData.renderElement : undefined;
} }
get renderNode(): any { get renderNode(): any {

View File

@ -34,7 +34,7 @@ export function textDef(ngContentIndex: number, constants: string[]): NodeDef {
parent: undefined, parent: undefined,
renderParent: undefined, renderParent: undefined,
bindingIndex: undefined, bindingIndex: undefined,
disposableIndex: undefined, outputIndex: undefined,
// regular values // regular values
flags: 0, flags: 0,
childFlags: 0, childFlags: 0,
@ -43,7 +43,7 @@ export function textDef(ngContentIndex: number, constants: string[]): NodeDef {
matchedQueryIds: 0, matchedQueryIds: 0,
references: {}, ngContentIndex, references: {}, ngContentIndex,
childCount: 0, bindings, childCount: 0, bindings,
disposableCount: 0, outputs: [],
element: undefined, element: undefined,
provider: undefined, provider: undefined,
text: {prefix: constants[0], source}, text: {prefix: constants[0], source},

View File

@ -40,7 +40,7 @@ export interface ViewDefinition {
reverseChildNodes: NodeDef[]; reverseChildNodes: NodeDef[];
lastRenderRootNode: NodeDef; lastRenderRootNode: NodeDef;
bindingCount: number; bindingCount: number;
disposableCount: number; outputCount: number;
/** /**
* Binary or of all query ids that are matched by one of the nodes. * Binary or of all query ids that are matched by one of the nodes.
* This includes query ids from templates as well. * This includes query ids from templates as well.
@ -99,8 +99,8 @@ export interface NodeDef {
bindingIndex: number; bindingIndex: number;
bindings: BindingDef[]; bindings: BindingDef[];
disposableIndex: number; outputIndex: number;
disposableCount: number; outputs: OutputDef[];
/** /**
* references that the user placed on the element * references that the user placed on the element
*/ */
@ -151,12 +151,13 @@ export enum NodeFlags {
AfterViewChecked = 1 << 7, AfterViewChecked = 1 << 7,
HasEmbeddedViews = 1 << 8, HasEmbeddedViews = 1 << 8,
HasComponent = 1 << 9, HasComponent = 1 << 9,
HasContentQuery = 1 << 10, IsComponent = 1 << 10,
HasStaticQuery = 1 << 11, HasContentQuery = 1 << 11,
HasDynamicQuery = 1 << 12, HasStaticQuery = 1 << 12,
HasViewQuery = 1 << 13, HasDynamicQuery = 1 << 13,
LazyProvider = 1 << 14, HasViewQuery = 1 << 14,
PrivateProvider = 1 << 15, LazyProvider = 1 << 15,
PrivateProvider = 1 << 16,
} }
export interface BindingDef { export interface BindingDef {
@ -173,11 +174,24 @@ export enum BindingType {
ElementClass, ElementClass,
ElementStyle, ElementStyle,
ElementProperty, ElementProperty,
ComponentHostProperty,
DirectiveProperty, DirectiveProperty,
TextInterpolation, TextInterpolation,
PureExpressionProperty PureExpressionProperty
} }
export interface OutputDef {
type: OutputType;
target: 'window'|'document'|'body'|'component';
eventName: string;
propName: string;
}
export enum OutputType {
ElementOutput,
DirectiveOutput
}
export enum QueryValueType { export enum QueryValueType {
ElementRef, ElementRef,
RenderElement, RenderElement,
@ -191,9 +205,11 @@ export interface ElementDef {
ns: string; ns: string;
/** ns, name, value */ /** ns, name, value */
attrs: [string, string, string][]; attrs: [string, string, string][];
outputs: ElementOutputDef[];
template: ViewDefinition; template: ViewDefinition;
component: NodeDef; componentProvider: NodeDef;
componentRendererType: RendererTypeV2;
// closure to allow recursive components
componentView: ViewDefinitionFactory;
/** /**
* visible public providers for DI in the view, * visible public providers for DI in the view,
* as see from this element. This does not include private providers. * as see from this element. This does not include private providers.
@ -208,11 +224,6 @@ export interface ElementDef {
handleEvent: ElementHandleEventFn; handleEvent: ElementHandleEventFn;
} }
export interface ElementOutputDef {
target: string;
eventName: string;
}
export type ElementHandleEventFn = (view: ViewData, eventName: string, event: any) => boolean; export type ElementHandleEventFn = (view: ViewData, eventName: string, event: any) => boolean;
export interface ProviderDef { export interface ProviderDef {
@ -221,10 +232,6 @@ export interface ProviderDef {
tokenKey: string; tokenKey: string;
value: any; value: any;
deps: DepDef[]; deps: DepDef[];
outputs: DirectiveOutputDef[];
rendererType: RendererTypeV2;
// closure to allow recursive components
component: ViewDefinitionFactory;
} }
export enum ProviderType { export enum ProviderType {
@ -250,11 +257,6 @@ export enum DepFlags {
Value = 2 << 2, Value = 2 << 2,
} }
export interface DirectiveOutputDef {
propName: string;
eventName: string;
}
export interface TextDef { export interface TextDef {
prefix: string; prefix: string;
source: string; source: string;
@ -369,6 +371,7 @@ export function asTextData(view: ViewData, index: number): TextData {
*/ */
export interface ElementData { export interface ElementData {
renderElement: any; renderElement: any;
componentView: ViewData;
embeddedViews: ViewData[]; embeddedViews: ViewData[];
// views that have been created from the template // views that have been created from the template
// of this element, // of this element,
@ -389,10 +392,7 @@ export function asElementData(view: ViewData, index: number): ElementData {
* *
* Attention: Adding fields to this is performance sensitive! * Attention: Adding fields to this is performance sensitive!
*/ */
export interface ProviderData { export interface ProviderData { instance: any; }
instance: any;
componentView: ViewData;
}
/** /**
* Accessor for view.nodes, enforcing that every usage site stays monomorphic. * Accessor for view.nodes, enforcing that every usage site stays monomorphic.

View File

@ -172,10 +172,10 @@ export function getParentRenderElement(view: ViewData, renderHost: any, def: Nod
let renderParent = def.renderParent; let renderParent = def.renderParent;
if (renderParent) { if (renderParent) {
const parent = def.parent; const parent = def.parent;
if (parent && (parent.type !== NodeType.Element || !parent.element.component || if (parent &&
(parent.element.component.provider.rendererType && (parent.type !== NodeType.Element || (parent.flags & NodeFlags.HasComponent) === 0 ||
parent.element.component.provider.rendererType.encapsulation === (parent.element.componentRendererType &&
ViewEncapsulation.Native))) { parent.element.componentRendererType.encapsulation === ViewEncapsulation.Native))) {
// only children of non components, or children of components with native encapsulation should // only children of non components, or children of components with native encapsulation should
// be attached. // be attached.
return asElementData(view, def.renderParent.index).renderElement; return asElementData(view, def.renderParent.index).renderElement;

View File

@ -9,14 +9,14 @@
import {ViewEncapsulation} from '../metadata/view'; import {ViewEncapsulation} from '../metadata/view';
import {RendererTypeV2, RendererV2} from '../render/api'; import {RendererTypeV2, RendererV2} from '../render/api';
import {checkAndUpdateElementDynamic, checkAndUpdateElementInline, createElement} from './element'; import {checkAndUpdateElementDynamic, checkAndUpdateElementInline, createElement, listenToElementOutputs} from './element';
import {expressionChangedAfterItHasBeenCheckedError} from './errors'; import {expressionChangedAfterItHasBeenCheckedError} from './errors';
import {appendNgContent} from './ng_content'; import {appendNgContent} from './ng_content';
import {callLifecycleHooksChildrenFirst, checkAndUpdateDirectiveDynamic, checkAndUpdateDirectiveInline, createDirectiveInstance, createPipeInstance, createProviderInstance} from './provider'; import {callLifecycleHooksChildrenFirst, checkAndUpdateDirectiveDynamic, checkAndUpdateDirectiveInline, createDirectiveInstance, createPipeInstance, createProviderInstance} from './provider';
import {checkAndUpdatePureExpressionDynamic, checkAndUpdatePureExpressionInline, createPureExpression} from './pure_expression'; import {checkAndUpdatePureExpressionDynamic, checkAndUpdatePureExpressionInline, createPureExpression} from './pure_expression';
import {checkAndUpdateQuery, createQuery, queryDef} from './query'; import {checkAndUpdateQuery, createQuery, queryDef} from './query';
import {checkAndUpdateTextDynamic, checkAndUpdateTextInline, createText} from './text'; 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'; import {checkBindingNoChanges, isComponentView, resolveViewDefinition, viewParentEl} from './util';
const NOOP = (): any => undefined; const NOOP = (): any => undefined;
@ -51,7 +51,7 @@ export function viewDef(
node.index = i; node.index = i;
node.parent = currentParent; node.parent = currentParent;
node.bindingIndex = viewBindingCount; node.bindingIndex = viewBindingCount;
node.disposableIndex = viewDisposableCount; node.outputIndex = viewDisposableCount;
node.reverseChildIndex = node.reverseChildIndex =
calculateReverseChildIndex(currentParent, i, node.childCount, nodes.length); calculateReverseChildIndex(currentParent, i, node.childCount, nodes.length);
@ -90,7 +90,7 @@ export function viewDef(
} }
viewBindingCount += node.bindings.length; viewBindingCount += node.bindings.length;
viewDisposableCount += node.disposableCount; viewDisposableCount += node.outputs.length;
if (!currentRenderParent && (node.type === NodeType.Element || node.type === NodeType.Text)) { if (!currentRenderParent && (node.type === NodeType.Element || node.type === NodeType.Text)) {
lastRenderRootNode = node; lastRenderRootNode = node;
@ -104,7 +104,7 @@ export function viewDef(
currentParent.element.allProviders = currentParent.element.publicProviders; currentParent.element.allProviders = currentParent.element.publicProviders;
} }
const isPrivateService = (node.flags & NodeFlags.PrivateProvider) !== 0; const isPrivateService = (node.flags & NodeFlags.PrivateProvider) !== 0;
const isComponent = (node.flags & NodeFlags.HasComponent) !== 0; const isComponent = (node.flags & NodeFlags.IsComponent) !== 0;
if (!isPrivateService || isComponent) { if (!isPrivateService || isComponent) {
currentParent.element.publicProviders[node.provider.tokenKey] = node; currentParent.element.publicProviders[node.provider.tokenKey] = node;
} else { } else {
@ -116,7 +116,7 @@ export function viewDef(
currentParent.element.allProviders[node.provider.tokenKey] = node; currentParent.element.allProviders[node.provider.tokenKey] = node;
} }
if (isComponent) { if (isComponent) {
currentParent.element.component = node; currentParent.element.componentProvider = node;
} }
} }
if (node.childCount) { if (node.childCount) {
@ -141,7 +141,7 @@ export function viewDef(
updateRenderer: updateRenderer || NOOP, updateRenderer: updateRenderer || NOOP,
handleEvent: handleEvent || NOOP, handleEvent: handleEvent || NOOP,
bindingCount: viewBindingCount, bindingCount: viewBindingCount,
disposableCount: viewDisposableCount, lastRenderRootNode outputCount: viewDisposableCount, lastRenderRootNode
}; };
} }
@ -242,7 +242,7 @@ function createView(
root: RootData, renderer: RendererV2, parent: ViewData, parentNodeDef: NodeDef, root: RootData, renderer: RendererV2, parent: ViewData, parentNodeDef: NodeDef,
def: ViewDefinition): ViewData { def: ViewDefinition): ViewData {
const nodes: NodeData[] = new Array(def.nodes.length); 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 = { const view: ViewData = {
def, def,
parent, parent,
@ -271,63 +271,66 @@ function createViewNodes(view: ViewData) {
for (let i = 0; i < def.nodes.length; i++) { for (let i = 0; i < def.nodes.length; i++) {
const nodeDef = def.nodes[i]; const nodeDef = def.nodes[i];
Services.setCurrentNode(view, i); Services.setCurrentNode(view, i);
let nodeData: any;
switch (nodeDef.type) { switch (nodeDef.type) {
case NodeType.Element: case NodeType.Element:
nodes[i] = createElement(view, renderHost, nodeDef) as any; const el = createElement(view, renderHost, nodeDef) as any;
break; let componentView: ViewData;
case NodeType.Text:
nodes[i] = createText(view, renderHost, nodeDef) as any;
break;
case NodeType.Provider: {
const instance = createProviderInstance(view, nodeDef);
const providerData = <ProviderData>{componentView: undefined, instance};
nodes[i] = providerData as any;
break;
}
case NodeType.Pipe: {
const instance = createPipeInstance(view, nodeDef);
const providerData = <ProviderData>{componentView: undefined, instance};
nodes[i] = providerData as any;
break;
}
case NodeType.Directive: {
if (nodeDef.flags & NodeFlags.HasComponent) { if (nodeDef.flags & NodeFlags.HasComponent) {
// Components can inject a ChangeDetectorRef that needs a references to const compViewDef = resolveViewDefinition(nodeDef.element.componentView);
// the component view. Therefore, we create the component view first const rendererType = nodeDef.element.componentRendererType;
// and set the ProviderData in ViewData, and then instantiate the provider.
const compViewDef = resolveViewDefinition(nodeDef.provider.component);
const rendererType = nodeDef.provider.rendererType;
let compRenderer: RendererV2; let compRenderer: RendererV2;
if (!rendererType) { if (!rendererType) {
compRenderer = view.root.renderer; compRenderer = view.root.renderer;
} else { } else {
const hostEl = asElementData(view, nodeDef.parent.index).renderElement; compRenderer = view.root.rendererFactory.createRenderer(el, rendererType);
compRenderer = view.root.rendererFactory.createRenderer(hostEl, rendererType);
} }
const componentView = createView(view.root, compRenderer, view, nodeDef, compViewDef); componentView = createView(
const providerData = <ProviderData>{componentView, instance: undefined}; view.root, compRenderer, view, nodeDef.element.componentProvider, compViewDef);
nodes[i] = providerData as any; }
const instance = providerData.instance = createDirectiveInstance(view, nodeDef); listenToElementOutputs(view, componentView, nodeDef, el);
initView(componentView, instance, instance); nodeData = <ElementData>{
} else { 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 = <ProviderData>{instance};
break;
}
case NodeType.Pipe: {
const instance = createPipeInstance(view, nodeDef);
nodeData = <ProviderData>{instance};
break;
}
case NodeType.Directive: {
const instance = createDirectiveInstance(view, nodeDef); const instance = createDirectiveInstance(view, nodeDef);
const providerData = <ProviderData>{componentView: undefined, instance}; nodeData = <ProviderData>{instance};
nodes[i] = providerData as any; if (nodeDef.flags & NodeFlags.IsComponent) {
const compView = asElementData(view, nodeDef.parent.index).componentView;
initView(componentView, instance, instance);
} }
break; break;
} }
case NodeType.PureExpression: case NodeType.PureExpression:
nodes[i] = createPureExpression(view, nodeDef) as any; nodeData = createPureExpression(view, nodeDef) as any;
break; break;
case NodeType.Query: case NodeType.Query:
nodes[i] = createQuery() as any; nodeData = createQuery() as any;
break; break;
case NodeType.NgContent: case NodeType.NgContent:
appendNgContent(view, renderHost, nodeDef); appendNgContent(view, renderHost, nodeDef);
// no runtime data needed for NgContent... // no runtime data needed for NgContent...
nodes[i] = undefined; nodeData = undefined;
break; break;
} }
nodes[i] = nodeData;
} }
// Create the ViewData.nodes of component views after we created everything else, // Create the ViewData.nodes of component views after we created everything else,
// so that e.g. ng-content works // so that e.g. ng-content works
@ -479,7 +482,7 @@ export function destroyView(view: ViewData) {
if (view.renderer.destroyNode) { if (view.renderer.destroyNode) {
destroyViewNodes(view); destroyViewNodes(view);
} }
if (view.parentNodeDef && view.parentNodeDef.flags & NodeFlags.HasComponent) { if (isComponentView(view)) {
view.renderer.destroy(); view.renderer.destroy();
} }
view.state |= ViewState.Destroyed; view.state |= ViewState.Destroyed;
@ -513,8 +516,7 @@ function execComponentViewsAction(view: ViewData, action: ViewAction) {
const nodeDef = def.nodes[i]; const nodeDef = def.nodes[i];
if (nodeDef.flags & NodeFlags.HasComponent) { if (nodeDef.flags & NodeFlags.HasComponent) {
// a leaf // a leaf
const providerData = asProviderData(view, i); callViewAction(asElementData(view, i).componentView, action);
callViewAction(providerData.componentView, action);
} else if ((nodeDef.childFlags & NodeFlags.HasComponent) === 0) { } else if ((nodeDef.childFlags & NodeFlags.HasComponent) === 0) {
// a parent with leafs // a parent with leafs
// no child is a component, // no child is a component,

View File

@ -81,7 +81,7 @@ function declareTests({useJit}: {useJit: boolean}) {
]); ]);
}); });
xit('should trigger a state change animation from void => state on the component host element', it('should trigger a state change animation from void => state on the component host element',
() => { () => {
@Component({ @Component({
selector: 'my-cmp', selector: 'my-cmp',
@ -89,8 +89,7 @@ function declareTests({useJit}: {useJit: boolean}) {
animations: [trigger( animations: [trigger(
'myAnimation', 'myAnimation',
[transition( [transition(
'a => b', 'a => b', [style({'opacity': '0'}), animate(500, style({'opacity': '1'}))])])],
[style({'opacity': '0'}), animate(500, style({'opacity': '1'}))])])],
}) })
class Cmp { class Cmp {
@HostBinding('@myAnimation') @HostBinding('@myAnimation')
@ -516,7 +515,7 @@ function declareTests({useJit}: {useJit: boolean}) {
expect(cmp.event2.triggerName).toBeTruthy('ani2'); 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', it('should trigger a state change listener for when the animation changes state from void => state on the host element',
() => { () => {
@Component({ @Component({
selector: 'my-cmp', selector: 'my-cmp',
@ -533,7 +532,7 @@ function declareTests({useJit}: {useJit: boolean}) {
@HostBinding('@myAnimation2') @HostBinding('@myAnimation2')
exp: any = false; exp: any = false;
@HostListener('@myAnimation2.start') @HostListener('@myAnimation2.start', ['$event'])
callback = (event: any) => { this.event = event; }; callback = (event: any) => { this.event = event; };
} }

View File

@ -7,20 +7,22 @@
*/ */
import {Injector, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation} from '@angular/core'; 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 {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {createRootView, isBrowser, removeNodes} from './helper'; import {createRootView, isBrowser, removeNodes} from './helper';
export function main() { export function main() {
describe(`Component Views`, () => { describe(
`Component Views`, () => {
function compViewDef( function compViewDef(
nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn, nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn,
viewFlags: ViewFlags = ViewFlags.None): ViewDefinition { viewFlags: ViewFlags = ViewFlags.None): ViewDefinition {
return viewDef(viewFlags, nodes, updateDirectives, updateRenderer); return viewDef(viewFlags, nodes, updateDirectives, updateRenderer);
} }
function createAndGetRootNodes(viewDef: ViewDefinition): {rootNodes: any[], view: ViewData} { function createAndGetRootNodes(viewDef: ViewDefinition):
{rootNodes: any[], view: ViewData} {
const view = createRootView(viewDef); const view = createRootView(viewDef);
const rootNodes = rootRenderNodes(view); const rootNodes = rootRenderNodes(view);
return {rootNodes, view}; return {rootNodes, view};
@ -33,15 +35,15 @@ export function main() {
} }
const {view, rootNodes} = createAndGetRootNodes(compViewDef([ const {view, rootNodes} = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 1, 'div'), elementDef(
directiveDef( NodeFlags.None, null, null, 1, 'div', null, null, null, null,
NodeFlags.None, null, 0, AComp, [], null, null,
() => compViewDef([ () => compViewDef([
elementDef(NodeFlags.None, null, null, 0, 'span'), elementDef(NodeFlags.None, null, null, 0, 'span'),
])), ])),
directiveDef(NodeFlags.IsComponent, null, 0, AComp, []),
])); ]));
const compView = asProviderData(view, 1).componentView; const compView = asElementData(view, 0).componentView;
expect(compView.context).toBe(instance); expect(compView.context).toBe(instance);
expect(compView.component).toBe(instance); expect(compView.component).toBe(instance);
@ -100,28 +102,30 @@ export function main() {
}); });
} }
describe('data binding', () => { describe(
it('should dirty check component views', () => { 'data binding', () => {
it('should dirty check component views',
() => {
let value: any; let value: any;
class AComp { class AComp {
a: any; a: any;
} }
const update = const update = jasmine.createSpy('updater').and.callFake(
jasmine.createSpy('updater').and.callFake((check: NodeCheckFn, view: ViewData) => { (check: NodeCheckFn, view: ViewData) => {
check(view, 0, ArgumentType.Inline, value); check(view, 0, ArgumentType.Inline, value);
}); });
const {view, rootNodes} = createAndGetRootNodes( const {view, rootNodes} = createAndGetRootNodes(
compViewDef([ compViewDef([
elementDef(NodeFlags.None, null, null, 1, 'div'), elementDef(NodeFlags.None, null, null, 1, 'div', null, null, null, null, () => compViewDef(
directiveDef(NodeFlags.None, null, 0, AComp, [], null, null, () => compViewDef(
[ [
elementDef(NodeFlags.None, null, null, 0, 'span', null, [[BindingType.ElementAttribute, 'a', SecurityContext.NONE]]), elementDef(NodeFlags.None, null, null, 0, 'span', null, [[BindingType.ElementAttribute, 'a', SecurityContext.NONE]]),
], null, update ], null, update
)), )),
directiveDef(NodeFlags.IsComponent, null, 0, AComp, []),
])); ]));
const compView = asProviderData(view, 1).componentView; const compView = asElementData(view, 0).componentView;
value = 'v1'; value = 'v1';
Services.checkAndUpdateView(view); Services.checkAndUpdateView(view);
@ -139,7 +143,8 @@ export function main() {
`ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'v1'. Current value: 'v2'.`); `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', () => { it('should support detaching and attaching component views for dirty checking',
() => {
class AComp { class AComp {
a: any; a: any;
} }
@ -147,17 +152,17 @@ export function main() {
const update = jasmine.createSpy('updater'); const update = jasmine.createSpy('updater');
const {view, rootNodes} = createAndGetRootNodes(compViewDef([ const {view, rootNodes} = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 1, 'div'), elementDef(
directiveDef( NodeFlags.None, null, null, 1, 'div', null, null, null, null,
NodeFlags.None, null, 0, AComp, [], null, null,
() => compViewDef( () => compViewDef(
[ [
elementDef(NodeFlags.None, null, null, 0, 'span'), elementDef(NodeFlags.None, null, null, 0, 'span'),
], ],
update)), update)),
directiveDef(NodeFlags.IsComponent, null, 0, AComp, [], null, null),
])); ]));
const compView = asProviderData(view, 1).componentView; const compView = asElementData(view, 0).componentView;
Services.checkAndUpdateView(view); Services.checkAndUpdateView(view);
update.calls.reset(); update.calls.reset();
@ -180,18 +185,23 @@ export function main() {
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( const {view} = createAndGetRootNodes(compViewDef(
[ [
elementDef(NodeFlags.None, null, null, 1, 'div'), elementDef(
directiveDef( NodeFlags.None, null, null, 1, 'div', null, null, null, null,
NodeFlags.None, null, 0, AComp, [], {a: [0, 'a']}, null, () => {
() => compViewDef( return compViewDef(
[ [
elementDef(NodeFlags.None, null, null, 0, 'span', null, null, ['click']), elementDef(
NodeFlags.None, null, null, 0, 'span', null, null,
[[null, 'click']]),
], ],
update, null, ViewFlags.OnPush)), update, null, ViewFlags.OnPush);
}),
directiveDef(NodeFlags.IsComponent, null, 0, AComp, [], {a: [0, 'a']}),
], ],
(check, view) => { check(view, 1, ArgumentType.Inline, compInputValue); })); (check, view) => { check(view, 1, ArgumentType.Inline, compInputValue); }));
@ -226,7 +236,8 @@ export function main() {
}); });
} }
it('should stop dirty checking views that threw errors in change detection', () => { it('should stop dirty checking views that threw errors in change detection',
() => {
class AComp { class AComp {
a: any; a: any;
} }
@ -234,19 +245,20 @@ export function main() {
const update = jasmine.createSpy('updater'); const update = jasmine.createSpy('updater');
const {view, rootNodes} = createAndGetRootNodes(compViewDef([ const {view, rootNodes} = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 1, 'div'), elementDef(NodeFlags.None, null, null, 1, 'div', null, null, null, null, () => compViewDef(
directiveDef(
NodeFlags.None, null, 0, AComp, [], null, null,
() => compViewDef(
[ [
elementDef(NodeFlags.None, null, null, 0, 'span', null, [[BindingType.ElementAttribute, 'a', SecurityContext.NONE]]), elementDef(NodeFlags.None, null, null, 0, 'span', null, [[BindingType.ElementAttribute, 'a', SecurityContext.NONE]]),
], ],
null, update)), null, update)),
directiveDef(
NodeFlags.IsComponent, null, 0, AComp, [], null, null,
),
])); ]));
const compView = asProviderData(view, 1).componentView; const compView = asElementData(view, 0).componentView;
update.and.callFake((check: NodeCheckFn, view: ViewData) => { throw new Error('Test'); }); update.and.callFake(
(check: NodeCheckFn, view: ViewData) => { throw new Error('Test'); });
expect(() => Services.checkAndUpdateView(view)).toThrowError('Test'); expect(() => Services.checkAndUpdateView(view)).toThrowError('Test');
expect(update).toHaveBeenCalled(); expect(update).toHaveBeenCalled();
@ -268,13 +280,13 @@ export function main() {
} }
const {view, rootNodes} = createAndGetRootNodes(compViewDef([ const {view, rootNodes} = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 1, 'div'), elementDef(
directiveDef( NodeFlags.None, null, null, 1, 'div', null, null, null, null,
NodeFlags.None, null, 0, AComp, [], null, null,
() => compViewDef([ () => compViewDef([
elementDef(NodeFlags.None, null, null, 1, 'span'), elementDef(NodeFlags.None, null, null, 1, 'span'),
directiveDef(NodeFlags.OnDestroy, null, 0, ChildProvider, []) directiveDef(NodeFlags.OnDestroy, null, 0, ChildProvider, [])
])), ])),
directiveDef(NodeFlags.IsComponent, null, 0, AComp, [], null, null, ),
])); ]));
Services.destroyView(view); Services.destroyView(view);

View File

@ -8,7 +8,7 @@
import {Injector, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation, WrappedValue, getDebugNode} from '@angular/core'; import {Injector, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation, WrappedValue, getDebugNode} from '@angular/core';
import {getDebugContext} from '@angular/core/src/errors'; 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 {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {ARG_TYPE_VALUES, checkNodeInlineOrDynamic, createRootView, isBrowser, removeNodes} from './helper'; import {ARG_TYPE_VALUES, checkNodeInlineOrDynamic, createRootView, isBrowser, removeNodes} from './helper';
@ -190,7 +190,8 @@ export function main() {
const removeListenerSpy = const removeListenerSpy =
spyOn(HTMLElement.prototype, 'removeEventListener').and.callThrough(); spyOn(HTMLElement.prototype, 'removeEventListener').and.callThrough();
const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef([elementDef( 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(); rootNodes[0].click();
@ -256,7 +257,7 @@ export function main() {
let preventDefaultSpy: jasmine.Spy; let preventDefaultSpy: jasmine.Spy;
const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef([elementDef( 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) => { (view, eventName, event) => {
preventDefaultSpy = spyOn(event, 'preventDefault').and.callThrough(); preventDefaultSpy = spyOn(event, 'preventDefault').and.callThrough();
return eventHandlerResult; return eventHandlerResult;
@ -281,10 +282,9 @@ export function main() {
it('should report debug info on event errors', () => { it('should report debug info on event errors', () => {
const addListenerSpy = spyOn(HTMLElement.prototype, 'addEventListener').and.callThrough(); const addListenerSpy = spyOn(HTMLElement.prototype, 'addEventListener').and.callThrough();
const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef( const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef([elementDef(
[elementDef(NodeFlags.None, null, null, 0, 'button', null, null, ['click'], () => { NodeFlags.None, null, null, 0, 'button', null, null, [[null, 'click']],
throw new Error('Test'); () => { throw new Error('Test'); })]));
})]));
let err: any; let err: any;
try { try {

View File

@ -30,9 +30,10 @@ export function main() {
const aCompViewDef = compViewDef(viewNodes); const aCompViewDef = compViewDef(viewNodes);
return [ return [
elementDef(NodeFlags.None, null, null, 1 + contentNodes.length, 'acomp'), elementDef(
directiveDef(NodeFlags.None, null, 0, AComp, [], null, null, () => aCompViewDef), NodeFlags.None, null, null, 1 + contentNodes.length, 'acomp', null, null, null, null,
...contentNodes () => 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]); const view0 = Services.createEmbeddedView(componentView, componentView.def.nodes[1]);
attachEmbeddedView(asElementData(componentView, 1), 0, view0); attachEmbeddedView(asElementData(componentView, 1), 0, view0);

View File

@ -113,10 +113,10 @@ export function main() {
try { try {
createRootView( createRootView(
compViewDef([ compViewDef([
elementDef(NodeFlags.None, null, null, 1, 'div'), elementDef(
directiveDef( NodeFlags.None, null, null, 1, 'div', null, null, null, null,
NodeFlags.None, null, 0, SomeService, [], null, null, () => compViewDef([textDef(null, ['a'])])),
() => compViewDef([textDef(null, ['a'])])) directiveDef(NodeFlags.IsComponent, null, 0, SomeService, [])
]), ]),
TestBed.get(Injector), [], getDOM().createElement('div')); TestBed.get(Injector), [], getDOM().createElement('div'));
} catch (e) { } catch (e) {
@ -174,13 +174,13 @@ export function main() {
it('should inject from a parent elment in a parent view', () => { it('should inject from a parent elment in a parent view', () => {
createAndGetRootNodes(compViewDef([ createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 1, 'div'), elementDef(
directiveDef( NodeFlags.None, null, null, 1, 'div', null, null, null, null,
NodeFlags.None, null, 0, Dep, [], null, null,
() => compViewDef([ () => compViewDef([
elementDef(NodeFlags.None, null, null, 1, 'span'), elementDef(NodeFlags.None, null, null, 1, 'span'),
directiveDef(NodeFlags.None, null, 0, SomeService, [Dep]) directiveDef(NodeFlags.None, null, 0, SomeService, [Dep])
])), ])),
directiveDef(NodeFlags.IsComponent, null, 0, Dep, []),
])); ]));
expect(instance.dep instanceof Dep).toBeTruthy(); expect(instance.dep instanceof Dep).toBeTruthy();
@ -275,24 +275,24 @@ export function main() {
it('should inject ChangeDetectorRef for component providers', () => { it('should inject ChangeDetectorRef for component providers', () => {
const {view, rootNodes} = createAndGetRootNodes(compViewDef([ const {view, rootNodes} = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 1, 'div'), elementDef(
directiveDef( NodeFlags.None, null, null, 1, 'div', null, null, null, null,
NodeFlags.None, null, 0, SomeService, [ChangeDetectorRef], null, null,
() => compViewDef([ () => compViewDef([
elementDef(NodeFlags.None, null, null, 0, 'span'), 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); expect(instance.dep._view).toBe(compView);
}); });
it('should inject RendererV1', () => { it('should inject RendererV1', () => {
createAndGetRootNodes(compViewDef([ createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 1, 'span'), elementDef(
directiveDef( NodeFlags.None, null, null, 1, 'span', null, null, null, null,
NodeFlags.None, null, 0, SomeService, [Renderer], null, null, () => compViewDef([anchorDef(NodeFlags.None, null, null, 0)])),
() => compViewDef([anchorDef(NodeFlags.None, null, null, 0)])) directiveDef(NodeFlags.IsComponent, null, 0, SomeService, [Renderer])
])); ]));
expect(instance.dep.createElement).toBeTruthy(); expect(instance.dep.createElement).toBeTruthy();
@ -300,10 +300,10 @@ export function main() {
it('should inject RendererV2', () => { it('should inject RendererV2', () => {
createAndGetRootNodes(compViewDef([ createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 1, 'span'), elementDef(
directiveDef( NodeFlags.None, null, null, 1, 'span', null, null, null, null,
NodeFlags.None, null, 0, SomeService, [RendererV2], null, null, () => compViewDef([anchorDef(NodeFlags.None, null, null, 0)])),
() => compViewDef([anchorDef(NodeFlags.None, null, null, 0)])) directiveDef(NodeFlags.IsComponent, null, 0, SomeService, [RendererV2])
])); ]));
expect(instance.dep.createElement).toBeTruthy(); expect(instance.dep.createElement).toBeTruthy();

View File

@ -50,16 +50,17 @@ export function main() {
]; ];
} }
function viewQueryProviders(nodes: NodeDef[]) { function compViewQueryProviders(extraChildCount: number, nodes: NodeDef[]) {
return [ return [
directiveDef( elementDef(
NodeFlags.None, null, 0, QueryService, [], null, null, NodeFlags.None, null, null, 1 + extraChildCount, 'div', null, null, null, null,
() => compViewDef([ () => compViewDef([
queryDef( queryDef(
NodeFlags.HasViewQuery | NodeFlags.HasDynamicQuery, someQueryId, NodeFlags.HasViewQuery | NodeFlags.HasDynamicQuery, someQueryId,
{'a': QueryBindingType.All}), {'a': QueryBindingType.All}),
...nodes ...nodes
])), ])),
directiveDef(NodeFlags.IsComponent, null, 0, QueryService, [], null, null, ),
]; ];
} }
@ -110,8 +111,9 @@ export function main() {
describe('view queries', () => { describe('view queries', () => {
it('should query providers in the view', () => { it('should query providers in the view', () => {
const {view} = createAndGetRootNodes(compViewDef([ const {view} = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 1, 'div'), ...compViewQueryProviders(
...viewQueryProviders([ 0,
[
elementDef(NodeFlags.None, null, null, 1, 'span'), elementDef(NodeFlags.None, null, null, 1, 'span'),
aServiceProvider(), aServiceProvider(),
]), ]),
@ -120,15 +122,16 @@ export function main() {
Services.checkAndUpdateView(view); Services.checkAndUpdateView(view);
const comp: QueryService = asProviderData(view, 1).instance; 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.length).toBe(1);
expect(comp.a.first).toBe(asProviderData(compView, 2).instance); expect(comp.a.first).toBe(asProviderData(compView, 2).instance);
}); });
it('should not query providers on the host element', () => { it('should not query providers on the host element', () => {
const {view} = createAndGetRootNodes(compViewDef([ const {view} = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 2, 'div'), ...compViewQueryProviders(
...viewQueryProviders([ 1,
[
elementDef(NodeFlags.None, null, null, 0, 'span'), elementDef(NodeFlags.None, null, null, 0, 'span'),
]), ]),
aServiceProvider(), aServiceProvider(),
@ -225,8 +228,9 @@ export function main() {
it('should update view queries if embedded views are added or removed', () => { it('should update view queries if embedded views are added or removed', () => {
const {view} = createAndGetRootNodes(compViewDef([ const {view} = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 1, 'div'), ...compViewQueryProviders(
...viewQueryProviders([ 0,
[
anchorDef(NodeFlags.HasEmbeddedViews, null, null, 0, null, embeddedViewDef([ anchorDef(NodeFlags.HasEmbeddedViews, null, null, 0, null, embeddedViewDef([
elementDef(NodeFlags.None, null, null, 1, 'div'), elementDef(NodeFlags.None, null, null, 1, 'div'),
aServiceProvider(), aServiceProvider(),
@ -239,7 +243,7 @@ export function main() {
const comp: QueryService = asProviderData(view, 1).instance; const comp: QueryService = asProviderData(view, 1).instance;
expect(comp.a.length).toBe(0); 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]); const childView = Services.createEmbeddedView(compView, compView.def.nodes[1]);
attachEmbeddedView(asElementData(compView, 1), 0, childView); attachEmbeddedView(asElementData(compView, 1), 0, childView);
Services.checkAndUpdateView(view); Services.checkAndUpdateView(view);

View File

@ -35,20 +35,20 @@ export function main() {
function createViewWithData() { function createViewWithData() {
const {view} = createAndGetRootNodes(compViewDef([ const {view} = createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 1, 'div'), elementDef(
directiveDef( NodeFlags.None, null, null, 1, 'div', null, null, null, null,
NodeFlags.None, null, 0, AComp, [], null, null,
() => compViewDef([ () => compViewDef([
elementDef(NodeFlags.None, [['ref', QueryValueType.ElementRef]], null, 2, 'span'), elementDef(NodeFlags.None, [['ref', QueryValueType.ElementRef]], null, 2, 'span'),
directiveDef(NodeFlags.None, null, 0, AService, []), textDef(null, ['a']) directiveDef(NodeFlags.None, null, 0, AService, []), textDef(null, ['a'])
])), ])),
directiveDef(NodeFlags.IsComponent, null, 0, AComp, []),
])); ]));
return view; return view;
} }
it('should provide data for elements', () => { it('should provide data for elements', () => {
const view = createViewWithData(); const view = createViewWithData();
const compView = asProviderData(view, 1).componentView; const compView = asElementData(view, 0).componentView;
const debugCtx = Services.createDebugContext(compView, 0); const debugCtx = Services.createDebugContext(compView, 0);
@ -65,7 +65,7 @@ export function main() {
it('should provide data for text nodes', () => { it('should provide data for text nodes', () => {
const view = createViewWithData(); const view = createViewWithData();
const compView = asProviderData(view, 1).componentView; const compView = asElementData(view, 0).componentView;
const debugCtx = Services.createDebugContext(compView, 2); 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', () => { it('should provide data for other nodes based on the nearest element parent', () => {
const view = createViewWithData(); const view = createViewWithData();
const compView = asProviderData(view, 1).componentView; const compView = asElementData(view, 0).componentView;
const debugCtx = Services.createDebugContext(compView, 1); const debugCtx = Services.createDebugContext(compView, 1);

View File

@ -26,8 +26,8 @@ let viewFlags = ViewFlags.None;
function TreeComponent_Host(): ViewDefinition { function TreeComponent_Host(): ViewDefinition {
return viewDef(viewFlags, [ return viewDef(viewFlags, [
elementDef(NodeFlags.None, null, null, 1, 'tree'), elementDef(NodeFlags.None, null, null, 1, 'tree', null, null, null, null, TreeComponent_0),
directiveDef(NodeFlags.None, null, 0, TreeComponent, [], null, null, TreeComponent_0), directiveDef(NodeFlags.IsComponent, null, 0, TreeComponent, []),
]); ]);
} }
@ -35,9 +35,8 @@ function TreeComponent_1() {
return viewDef( return viewDef(
viewFlags, viewFlags,
[ [
elementDef(NodeFlags.None, null, null, 1, 'tree'), elementDef(NodeFlags.None, null, null, 1, 'tree', null, null, null, null, TreeComponent_0),
directiveDef( directiveDef(NodeFlags.IsComponent, null, 0, TreeComponent, [], {data: [0, 'data']}),
NodeFlags.None, null, 0, TreeComponent, [], {data: [0, 'data']}, null, TreeComponent_0),
], ],
(check, view) => { (check, view) => {
const cmp = view.component; const cmp = view.component;
@ -49,9 +48,8 @@ function TreeComponent_2() {
return viewDef( return viewDef(
viewFlags, viewFlags,
[ [
elementDef(NodeFlags.None, null, null, 1, 'tree'), elementDef(NodeFlags.None, null, null, 1, 'tree', null, null, null, null, TreeComponent_0),
directiveDef( directiveDef(NodeFlags.IsComponent, null, 0, TreeComponent, [], {data: [0, 'data']}),
NodeFlags.None, null, 0, TreeComponent, [], {data: [0, 'data']}, null, TreeComponent_0),
], ],
(check, view) => { (check, view) => {
const cmp = view.component; const cmp = view.component;