refactor(compiler): generate host listeners in DirectiveWrappers

Part of #11683
This commit is contained in:
Tobias Bosch 2016-10-26 16:58:35 -07:00 committed by vsavkin
parent a664aba2c9
commit 32feb8a532
15 changed files with 628 additions and 316 deletions

View File

@ -71,7 +71,7 @@ export function convertPropertyBinding(
} }
export class ConvertActionBindingResult { export class ConvertActionBindingResult {
constructor(public stmts: o.Statement[], public preventDefault: o.Expression) {} constructor(public stmts: o.Statement[], public preventDefault: o.ReadVarExpr) {}
} }
/** /**

View File

@ -10,7 +10,8 @@ import {SecurityContext} from '@angular/core';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
import {Identifiers, resolveIdentifier} from '../identifiers'; import {Identifiers, resolveIdentifier} from '../identifiers';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {BoundElementPropertyAst, PropertyBindingType} from '../template_parser/template_ast'; import {EMPTY_STATE as EMPTY_ANIMATION_STATE} from '../private_import_core';
import {BoundElementPropertyAst, BoundEventAst, PropertyBindingType} from '../template_parser/template_ast';
import {createEnumExpression} from './identifier_util'; import {createEnumExpression} from './identifier_util';
@ -87,3 +88,59 @@ function sanitizedValue(
let args = [securityContextExpression, renderValue]; let args = [securityContextExpression, renderValue];
return ctx.callMethod('sanitize', args); return ctx.callMethod('sanitize', args);
} }
export function triggerAnimation(
view: o.Expression, componentView: o.Expression, boundProp: BoundElementPropertyAst,
eventListener: o.Expression, renderElement: o.Expression, renderValue: o.Expression,
lastRenderValue: o.Expression) {
const detachStmts: o.Statement[] = [];
const updateStmts: o.Statement[] = [];
const animationName = boundProp.name;
const animationFnExpr =
componentView.prop('componentType').prop('animations').key(o.literal(animationName));
// it's important to normalize the void value as `void` explicitly
// so that the styles data can be obtained from the stringmap
const emptyStateValue = o.literal(EMPTY_ANIMATION_STATE);
const unitializedValue = o.importExpr(resolveIdentifier(Identifiers.UNINITIALIZED));
const animationTransitionVar = o.variable('animationTransition_' + animationName);
updateStmts.push(
animationTransitionVar
.set(animationFnExpr.callFn([
view, renderElement,
lastRenderValue.equals(unitializedValue).conditional(emptyStateValue, lastRenderValue),
renderValue.equals(unitializedValue).conditional(emptyStateValue, renderValue)
]))
.toDeclStmt());
detachStmts.push(
animationTransitionVar
.set(animationFnExpr.callFn([view, renderElement, lastRenderValue, emptyStateValue]))
.toDeclStmt());
const registerStmts = [
animationTransitionVar
.callMethod(
'onStart',
[eventListener.callMethod(
o.BuiltinMethod.Bind,
[view, o.literal(BoundEventAst.calcFullName(animationName, null, 'start'))])])
.toStmt(),
animationTransitionVar
.callMethod(
'onDone',
[eventListener.callMethod(
o.BuiltinMethod.Bind,
[view, o.literal(BoundEventAst.calcFullName(animationName, null, 'done'))])])
.toStmt(),
];
updateStmts.push(...registerStmts);
detachStmts.push(...registerStmts);
return {updateStmts, detachStmts};
}

View File

@ -10,8 +10,8 @@ import {Injectable} from '@angular/core';
import {CompileDirectiveMetadata, CompileIdentifierMetadata} from './compile_metadata'; import {CompileDirectiveMetadata, CompileIdentifierMetadata} from './compile_metadata';
import {createCheckBindingField, createCheckBindingStmt} from './compiler_util/binding_util'; import {createCheckBindingField, createCheckBindingStmt} from './compiler_util/binding_util';
import {convertPropertyBinding} from './compiler_util/expression_converter'; import {EventHandlerVars, convertActionBinding, convertPropertyBinding} from './compiler_util/expression_converter';
import {writeToRenderer} from './compiler_util/render_util'; import {triggerAnimation, writeToRenderer} from './compiler_util/render_util';
import {CompilerConfig} from './config'; import {CompilerConfig} from './config';
import {Parser} from './expression_parser/parser'; import {Parser} from './expression_parser/parser';
import {Identifiers, resolveIdentifier} from './identifiers'; import {Identifiers, resolveIdentifier} from './identifiers';
@ -31,12 +31,15 @@ export class DirectiveWrapperCompileResult {
const CONTEXT_FIELD_NAME = 'context'; const CONTEXT_FIELD_NAME = 'context';
const CHANGES_FIELD_NAME = 'changes'; const CHANGES_FIELD_NAME = 'changes';
const CHANGED_FIELD_NAME = 'changed'; const CHANGED_FIELD_NAME = 'changed';
const EVENT_HANDLER_FIELD_NAME = 'eventHandler';
const CURR_VALUE_VAR = o.variable('currValue'); const CURR_VALUE_VAR = o.variable('currValue');
const THROW_ON_CHANGE_VAR = o.variable('throwOnChange'); const THROW_ON_CHANGE_VAR = o.variable('throwOnChange');
const FORCE_UPDATE_VAR = o.variable('forceUpdate'); const FORCE_UPDATE_VAR = o.variable('forceUpdate');
const VIEW_VAR = o.variable('view'); const VIEW_VAR = o.variable('view');
const COMPONENT_VIEW_VAR = o.variable('componentView');
const RENDER_EL_VAR = o.variable('el'); const RENDER_EL_VAR = o.variable('el');
const EVENT_NAME_VAR = o.variable('eventName');
const RESET_CHANGES_STMT = o.THIS_EXPR.prop(CHANGES_FIELD_NAME).set(o.literalMap([])).toStmt(); const RESET_CHANGES_STMT = o.THIS_EXPR.prop(CHANGES_FIELD_NAME).set(o.literalMap([])).toStmt();
@ -57,22 +60,17 @@ export class DirectiveWrapperCompiler {
private _schemaRegistry: ElementSchemaRegistry, private _console: Console) {} private _schemaRegistry: ElementSchemaRegistry, private _console: Console) {}
compile(dirMeta: CompileDirectiveMetadata): DirectiveWrapperCompileResult { compile(dirMeta: CompileDirectiveMetadata): DirectiveWrapperCompileResult {
const hostParseResult = parseHostBindings(dirMeta, this._exprParser, this._schemaRegistry);
reportParseErrors(hostParseResult.errors, this._console);
const builder = new DirectiveWrapperBuilder(this.compilerConfig, dirMeta); const builder = new DirectiveWrapperBuilder(this.compilerConfig, dirMeta);
Object.keys(dirMeta.inputs).forEach((inputFieldName) => { Object.keys(dirMeta.inputs).forEach((inputFieldName) => {
addCheckInputMethod(inputFieldName, builder); addCheckInputMethod(inputFieldName, builder);
}); });
addDetectChangesInInputPropsMethod(builder); addNgDoCheckMethod(builder);
addCheckHostMethod(hostParseResult.hostProps, builder);
const hostParseResult = parseHostBindings(dirMeta, this._exprParser, this._schemaRegistry); addHandleEventMethod(hostParseResult.hostListeners, builder);
reportParseErrors(hostParseResult.errors, this._console); addSubscribeMethod(dirMeta, builder);
// host properties are change detected by the DirectiveWrappers,
// except for the animation properties as they need close integration with animation events
// and DirectiveWrappers don't support
// event listeners right now.
addDetectChangesInHostPropsMethod(
hostParseResult.hostProps.filter(hostProp => !hostProp.isAnimation), builder);
// TODO(tbosch): implement hostListeners via DirectiveWrapper as well!
const classStmt = builder.build(); const classStmt = builder.build();
return new DirectiveWrapperCompileResult([classStmt], classStmt.name); return new DirectiveWrapperCompileResult([classStmt], classStmt.name);
@ -84,11 +82,14 @@ class DirectiveWrapperBuilder implements ClassBuilder {
getters: o.ClassGetter[] = []; getters: o.ClassGetter[] = [];
methods: o.ClassMethod[] = []; methods: o.ClassMethod[] = [];
ctorStmts: o.Statement[] = []; ctorStmts: o.Statement[] = [];
detachStmts: o.Statement[] = [];
destroyStmts: o.Statement[] = [];
genChanges: boolean; genChanges: boolean;
ngOnChanges: boolean; ngOnChanges: boolean;
ngOnInit: boolean; ngOnInit: boolean;
ngDoCheck: boolean; ngDoCheck: boolean;
ngOnDestroy: boolean;
constructor(public compilerConfig: CompilerConfig, public dirMeta: CompileDirectiveMetadata) { constructor(public compilerConfig: CompilerConfig, public dirMeta: CompileDirectiveMetadata) {
const dirLifecycleHooks = dirMeta.type.lifecycleHooks; const dirLifecycleHooks = dirMeta.type.lifecycleHooks;
@ -97,6 +98,11 @@ class DirectiveWrapperBuilder implements ClassBuilder {
this.ngOnChanges = dirLifecycleHooks.indexOf(LifecycleHooks.OnChanges) !== -1; this.ngOnChanges = dirLifecycleHooks.indexOf(LifecycleHooks.OnChanges) !== -1;
this.ngOnInit = dirLifecycleHooks.indexOf(LifecycleHooks.OnInit) !== -1; this.ngOnInit = dirLifecycleHooks.indexOf(LifecycleHooks.OnInit) !== -1;
this.ngDoCheck = dirLifecycleHooks.indexOf(LifecycleHooks.DoCheck) !== -1; this.ngDoCheck = dirLifecycleHooks.indexOf(LifecycleHooks.DoCheck) !== -1;
this.ngOnDestroy = dirLifecycleHooks.indexOf(LifecycleHooks.OnDestroy) !== -1;
if (this.ngOnDestroy) {
this.destroyStmts.push(
o.THIS_EXPR.prop(CONTEXT_FIELD_NAME).callMethod('ngOnDestroy', []).toStmt());
}
} }
build(): o.ClassStmt { build(): o.ClassStmt {
@ -105,7 +111,25 @@ class DirectiveWrapperBuilder implements ClassBuilder {
dirDepParamNames.push(`p${i}`); dirDepParamNames.push(`p${i}`);
} }
const methods = [
new o.ClassMethod(
'ngOnDetach',
[
new o.FnParam(
VIEW_VAR.name,
o.importType(resolveIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
new o.FnParam(
COMPONENT_VIEW_VAR.name,
o.importType(resolveIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
new o.FnParam(RENDER_EL_VAR.name, o.DYNAMIC_TYPE),
],
this.detachStmts),
new o.ClassMethod('ngOnDestroy', [], this.destroyStmts),
];
const fields: o.ClassField[] = [ const fields: o.ClassField[] = [
new o.ClassField(EVENT_HANDLER_FIELD_NAME, o.FUNCTION_TYPE),
new o.ClassField(CONTEXT_FIELD_NAME, o.importType(this.dirMeta.type)), new o.ClassField(CONTEXT_FIELD_NAME, o.importType(this.dirMeta.type)),
new o.ClassField(CHANGED_FIELD_NAME, o.BOOL_TYPE), new o.ClassField(CHANGED_FIELD_NAME, o.BOOL_TYPE),
]; ];
@ -125,12 +149,12 @@ class DirectiveWrapperBuilder implements ClassBuilder {
return createClassStmt({ return createClassStmt({
name: DirectiveWrapperCompiler.dirWrapperClassName(this.dirMeta.type), name: DirectiveWrapperCompiler.dirWrapperClassName(this.dirMeta.type),
ctorParams: dirDepParamNames.map((paramName) => new o.FnParam(paramName, o.DYNAMIC_TYPE)), ctorParams: dirDepParamNames.map((paramName) => new o.FnParam(paramName, o.DYNAMIC_TYPE)),
builders: [{fields, ctorStmts}, this] builders: [{fields, ctorStmts, methods}, this]
}); });
} }
} }
function addDetectChangesInInputPropsMethod(builder: DirectiveWrapperBuilder) { function addNgDoCheckMethod(builder: DirectiveWrapperBuilder) {
const changedVar = o.variable('changed'); const changedVar = o.variable('changed');
const stmts: o.Statement[] = [ const stmts: o.Statement[] = [
changedVar.set(o.THIS_EXPR.prop(CHANGED_FIELD_NAME)).toDeclStmt(), changedVar.set(o.THIS_EXPR.prop(CHANGED_FIELD_NAME)).toDeclStmt(),
@ -170,7 +194,7 @@ function addDetectChangesInInputPropsMethod(builder: DirectiveWrapperBuilder) {
stmts.push(new o.ReturnStatement(changedVar)); stmts.push(new o.ReturnStatement(changedVar));
builder.methods.push(new o.ClassMethod( builder.methods.push(new o.ClassMethod(
'detectChangesInInputProps', 'ngDoCheck',
[ [
new o.FnParam( new o.FnParam(
VIEW_VAR.name, o.importType(resolveIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])), VIEW_VAR.name, o.importType(resolveIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
@ -207,16 +231,19 @@ function addCheckInputMethod(input: string, builder: DirectiveWrapperBuilder) {
methodBody)); methodBody));
} }
function addDetectChangesInHostPropsMethod( function addCheckHostMethod(
hostProps: BoundElementPropertyAst[], builder: DirectiveWrapperBuilder) { hostProps: BoundElementPropertyAst[], builder: DirectiveWrapperBuilder) {
const stmts: o.Statement[] = []; const stmts: o.Statement[] = [];
const methodParams: o.FnParam[] = [ const methodParams: o.FnParam[] = [
new o.FnParam( new o.FnParam(
VIEW_VAR.name, o.importType(resolveIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])), VIEW_VAR.name, o.importType(resolveIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
new o.FnParam(
COMPONENT_VIEW_VAR.name,
o.importType(resolveIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
new o.FnParam(RENDER_EL_VAR.name, o.DYNAMIC_TYPE), new o.FnParam(RENDER_EL_VAR.name, o.DYNAMIC_TYPE),
new o.FnParam(THROW_ON_CHANGE_VAR.name, o.BOOL_TYPE), new o.FnParam(THROW_ON_CHANGE_VAR.name, o.BOOL_TYPE),
]; ];
hostProps.forEach((hostProp) => { hostProps.forEach((hostProp, hostPropIdx) => {
const field = createCheckBindingField(builder); const field = createCheckBindingField(builder);
const evalResult = convertPropertyBinding( const evalResult = convertPropertyBinding(
builder, null, o.THIS_EXPR.prop(CONTEXT_FIELD_NAME), hostProp.value, field.bindingId); builder, null, o.THIS_EXPR.prop(CONTEXT_FIELD_NAME), hostProp.value, field.bindingId);
@ -229,13 +256,80 @@ function addDetectChangesInHostPropsMethod(
methodParams.push(new o.FnParam( methodParams.push(new o.FnParam(
securityContextExpr.name, o.importType(resolveIdentifier(Identifiers.SecurityContext)))); securityContextExpr.name, o.importType(resolveIdentifier(Identifiers.SecurityContext))));
} }
let checkBindingStmts: o.Statement[];
if (hostProp.isAnimation) {
const {updateStmts, detachStmts} = triggerAnimation(
VIEW_VAR, COMPONENT_VIEW_VAR, hostProp,
o.THIS_EXPR.prop(EVENT_HANDLER_FIELD_NAME)
.or(o.importExpr(resolveIdentifier(Identifiers.noop))),
RENDER_EL_VAR, evalResult.currValExpr, field.expression);
checkBindingStmts = updateStmts;
builder.detachStmts.push(...detachStmts);
} else {
checkBindingStmts = writeToRenderer(
VIEW_VAR, hostProp, RENDER_EL_VAR, evalResult.currValExpr,
builder.compilerConfig.logBindingUpdate, securityContextExpr);
}
stmts.push(...createCheckBindingStmt( stmts.push(...createCheckBindingStmt(
evalResult, field.expression, THROW_ON_CHANGE_VAR, evalResult, field.expression, THROW_ON_CHANGE_VAR, checkBindingStmts));
writeToRenderer(
VIEW_VAR, hostProp, RENDER_EL_VAR, evalResult.currValExpr,
builder.compilerConfig.logBindingUpdate, securityContextExpr)));
}); });
builder.methods.push(new o.ClassMethod('detectChangesInHostProps', methodParams, stmts)); builder.methods.push(new o.ClassMethod('checkHost', methodParams, stmts));
}
function addHandleEventMethod(hostListeners: BoundEventAst[], builder: DirectiveWrapperBuilder) {
const resultVar = o.variable(`result`);
const actionStmts: o.Statement[] = [resultVar.set(o.literal(true)).toDeclStmt(o.BOOL_TYPE)];
hostListeners.forEach((hostListener, eventIdx) => {
const evalResult = convertActionBinding(
builder, null, o.THIS_EXPR.prop(CONTEXT_FIELD_NAME), hostListener.handler,
`sub_${eventIdx}`);
const trueStmts = evalResult.stmts;
if (evalResult.preventDefault) {
trueStmts.push(resultVar.set(evalResult.preventDefault.and(resultVar)).toStmt());
}
// TODO(tbosch): convert this into a `switch` once our OutputAst supports it.
actionStmts.push(
new o.IfStmt(EVENT_NAME_VAR.equals(o.literal(hostListener.fullName)), trueStmts));
});
actionStmts.push(new o.ReturnStatement(resultVar));
builder.methods.push(new o.ClassMethod(
'handleEvent',
[
new o.FnParam(EVENT_NAME_VAR.name, o.STRING_TYPE),
new o.FnParam(EventHandlerVars.event.name, o.DYNAMIC_TYPE)
],
actionStmts, o.BOOL_TYPE));
}
function addSubscribeMethod(dirMeta: CompileDirectiveMetadata, builder: DirectiveWrapperBuilder) {
const methodParams: o.FnParam[] = [new o.FnParam(EVENT_HANDLER_FIELD_NAME, o.DYNAMIC_TYPE)];
const stmts: o.Statement[] = [
o.THIS_EXPR.prop(EVENT_HANDLER_FIELD_NAME).set(o.variable(EVENT_HANDLER_FIELD_NAME)).toStmt()
];
Object.keys(dirMeta.outputs).forEach((emitterPropName, emitterIdx) => {
const eventName = dirMeta.outputs[emitterPropName];
const paramName = `emit${emitterIdx}`;
methodParams.push(new o.FnParam(paramName, o.BOOL_TYPE));
const subscriptionFieldName = `subscription${emitterIdx}`;
builder.fields.push(new o.ClassField(subscriptionFieldName, o.DYNAMIC_TYPE));
stmts.push(new o.IfStmt(
o.variable(paramName),
[o.THIS_EXPR.prop(subscriptionFieldName)
.set(o.THIS_EXPR.prop(CONTEXT_FIELD_NAME)
.prop(emitterPropName)
.callMethod(
o.BuiltinMethod.SubscribeObservable,
[o.variable(EVENT_HANDLER_FIELD_NAME)
.callMethod(
o.BuiltinMethod.Bind, [o.NULL_EXPR, o.literal(eventName)])]))
.toStmt()]));
builder.destroyStmts.push(
o.THIS_EXPR.prop(subscriptionFieldName)
.and(o.THIS_EXPR.prop(subscriptionFieldName).callMethod('unsubscribe', []))
.toStmt());
});
builder.methods.push(new o.ClassMethod('subscribe', methodParams, stmts));
} }
class ParseResult { class ParseResult {
@ -274,4 +368,85 @@ function reportParseErrors(parseErrors: ParseError[], console: Console) {
if (errors.length > 0) { if (errors.length > 0) {
throw new Error(`Directive parse errors:\n${errors.join('\n')}`); throw new Error(`Directive parse errors:\n${errors.join('\n')}`);
} }
} }
export class DirectiveWrapperExpressions {
static create(dir: CompileIdentifierMetadata, depsExpr: o.Expression[]): o.Expression {
return o.importExpr(dir).instantiate(depsExpr, o.importType(dir));
}
static context(dirWrapper: o.Expression): o.ReadPropExpr {
return dirWrapper.prop(CONTEXT_FIELD_NAME);
}
static ngDoCheck(
dirWrapper: o.Expression, view: o.Expression, renderElement: o.Expression,
throwOnChange: o.Expression): o.Expression {
return dirWrapper.callMethod('ngDoCheck', [view, renderElement, throwOnChange]);
}
static checkHost(
hostProps: BoundElementPropertyAst[], dirWrapper: o.Expression, view: o.Expression,
componentView: o.Expression, renderElement: o.Expression, throwOnChange: o.Expression,
runtimeSecurityContexts: o.Expression[]): o.Statement[] {
if (hostProps.length) {
return [dirWrapper
.callMethod(
'checkHost', [view, componentView, renderElement, throwOnChange].concat(
runtimeSecurityContexts))
.toStmt()];
} else {
return [];
}
}
static ngOnDetach(
hostProps: BoundElementPropertyAst[], dirWrapper: o.Expression, view: o.Expression,
componentView: o.Expression, renderEl: o.Expression): o.Statement[] {
if (hostProps.some(prop => prop.isAnimation)) {
return [dirWrapper
.callMethod(
'ngOnDetach',
[
view,
componentView,
renderEl,
])
.toStmt()];
} else {
return [];
}
}
static ngOnDestroy(dir: CompileDirectiveMetadata, dirWrapper: o.Expression): o.Statement[] {
if (dir.type.lifecycleHooks.indexOf(LifecycleHooks.OnDestroy) !== -1 ||
Object.keys(dir.outputs).length > 0) {
return [dirWrapper.callMethod('ngOnDestroy', []).toStmt()];
} else {
return [];
}
}
static subscribe(
dirMeta: CompileDirectiveMetadata, hostProps: BoundElementPropertyAst[], usedEvents: string[],
dirWrapper: o.Expression, eventListener: o.Expression): o.Statement[] {
let needsSubscribe = false;
let eventFlags: o.Expression[] = [];
Object.keys(dirMeta.outputs).forEach((propName) => {
const eventName = dirMeta.outputs[propName];
const eventUsed = usedEvents.indexOf(eventName) > -1;
needsSubscribe = needsSubscribe || eventUsed;
eventFlags.push(o.literal(eventUsed));
});
hostProps.forEach((hostProp) => {
if (hostProp.isAnimation && usedEvents.length > 0) {
needsSubscribe = true;
}
});
if (needsSubscribe) {
return [dirWrapper.callMethod('subscribe', [eventListener].concat(eventFlags)).toStmt()];
} else {
return [];
}
}
static handleEvent(
hostEvents: BoundEventAst[], dirWrapper: o.Expression, eventName: o.Expression,
event: o.Expression): o.Expression {
return dirWrapper.callMethod('handleEvent', [eventName, event]);
}
}

View File

@ -313,6 +313,13 @@ export class Identifiers {
moduleUrl: VIEW_UTILS_MODULE_URL, moduleUrl: VIEW_UTILS_MODULE_URL,
runtime: view_utils.InlineArrayDynamic runtime: view_utils.InlineArrayDynamic
}; };
static subscribeToRenderElement: IdentifierSpec = {
name: 'subscribeToRenderElement',
moduleUrl: VIEW_UTILS_MODULE_URL,
runtime: view_utils.subscribeToRenderElement
};
static noop:
IdentifierSpec = {name: 'noop', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: view_utils.noop};
} }
export function assetUrl(pkg: string, path: string = null, type: string = 'src'): string { export function assetUrl(pkg: string, path: string = null, type: string = 'src'): string {

View File

@ -152,13 +152,13 @@ class StatementInterpreter implements o.StatementVisitor, o.ExpressionVisitor {
if (isPresent(expr.builtin)) { if (isPresent(expr.builtin)) {
switch (expr.builtin) { switch (expr.builtin) {
case o.BuiltinMethod.ConcatArray: case o.BuiltinMethod.ConcatArray:
result = receiver.concat(args[0]); result = receiver.concat(...args);
break; break;
case o.BuiltinMethod.SubscribeObservable: case o.BuiltinMethod.SubscribeObservable:
result = receiver.subscribe({next: args[0]}); result = receiver.subscribe({next: args[0]});
break; break;
case o.BuiltinMethod.Bind: case o.BuiltinMethod.Bind:
result = receiver.bind(args[0]); result = receiver.bind(...args);
break; break;
default: default:
throw new Error(`Unknown builtin method ${expr.builtin}`); throw new Error(`Unknown builtin method ${expr.builtin}`);

View File

@ -76,19 +76,23 @@ export class BoundElementPropertyAst implements TemplateAst {
* `(@trigger.phase)="callback($event)"`). * `(@trigger.phase)="callback($event)"`).
*/ */
export class BoundEventAst implements TemplateAst { export class BoundEventAst implements TemplateAst {
static calcFullName(name: string, target: string, phase: string): string {
if (target) {
return `${target}:${name}`;
} else if (phase) {
return `@${name}.${phase}`;
} else {
return name;
}
}
constructor( constructor(
public name: string, public target: string, public phase: string, public handler: AST, public name: string, public target: string, public phase: string, public handler: AST,
public sourceSpan: ParseSourceSpan) {} public sourceSpan: ParseSourceSpan) {}
visit(visitor: TemplateAstVisitor, context: any): any { visit(visitor: TemplateAstVisitor, context: any): any {
return visitor.visitEvent(this, context); return visitor.visitEvent(this, context);
} }
get fullName() { get fullName() { return BoundEventAst.calcFullName(this.name, this.target, this.phase); }
if (this.target) {
return `${this.target}:${this.name}`;
} else {
return this.name;
}
}
get isAnimation(): boolean { return !!this.phase; } get isAnimation(): boolean { return !!this.phase; }
} }

View File

@ -9,7 +9,7 @@
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileIdentifierMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileTokenMetadata} from '../compile_metadata'; import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileIdentifierMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileTokenMetadata} from '../compile_metadata';
import {createDiTokenExpression} from '../compiler_util/identifier_util'; import {createDiTokenExpression} from '../compiler_util/identifier_util';
import {DirectiveWrapperCompiler} from '../directive_wrapper_compiler'; import {DirectiveWrapperCompiler, DirectiveWrapperExpressions} from '../directive_wrapper_compiler';
import {MapWrapper} from '../facade/collection'; import {MapWrapper} from '../facade/collection';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
import {Identifiers, identifierToken, resolveIdentifier, resolveIdentifierToken} from '../identifiers'; import {Identifiers, identifierToken, resolveIdentifier, resolveIdentifierToken} from '../identifiers';
@ -188,8 +188,7 @@ export class CompileElement extends CompileNode {
{name: DirectiveWrapperCompiler.dirWrapperClassName(provider.useClass)}); {name: DirectiveWrapperCompiler.dirWrapperClassName(provider.useClass)});
this._targetDependencies.push( this._targetDependencies.push(
new DirectiveWrapperDependency(provider.useClass, directiveWrapperIdentifier)); new DirectiveWrapperDependency(provider.useClass, directiveWrapperIdentifier));
return o.importExpr(directiveWrapperIdentifier) return DirectiveWrapperExpressions.create(directiveWrapperIdentifier, depsExpr);
.instantiate(depsExpr, o.importType(directiveWrapperIdentifier));
} else { } else {
return o.importExpr(provider.useClass) return o.importExpr(provider.useClass)
.instantiate(depsExpr, o.importType(provider.useClass)); .instantiate(depsExpr, o.importType(provider.useClass));
@ -204,7 +203,8 @@ export class CompileElement extends CompileNode {
resolvedProvider.eager, this); resolvedProvider.eager, this);
if (isDirectiveWrapper) { if (isDirectiveWrapper) {
this.directiveWrapperInstance.set(resolvedProvider.token.reference, instance); this.directiveWrapperInstance.set(resolvedProvider.token.reference, instance);
this.instances.set(resolvedProvider.token.reference, instance.prop('context')); this.instances.set(
resolvedProvider.token.reference, DirectiveWrapperExpressions.context(instance));
} else { } else {
this.instances.set(resolvedProvider.token.reference, instance); this.instances.set(resolvedProvider.token.reference, instance);
} }

View File

@ -8,16 +8,11 @@
import {ChangeDetectionStrategy, ViewEncapsulation} from '@angular/core'; import {ChangeDetectionStrategy, ViewEncapsulation} from '@angular/core';
import {CompileIdentifierMetadata} from '../compile_metadata';
import {createEnumExpression} from '../compiler_util/identifier_util'; import {createEnumExpression} from '../compiler_util/identifier_util';
import {Identifiers, resolveEnumIdentifier} from '../identifiers'; import {Identifiers} from '../identifiers';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {ChangeDetectorStatus, ViewType} from '../private_import_core'; import {ChangeDetectorStatus, ViewType} from '../private_import_core';
function _enumExpression(classIdentifier: CompileIdentifierMetadata, name: string): o.Expression {
return o.importExpr(resolveEnumIdentifier(classIdentifier, name));
}
export class ViewTypeEnum { export class ViewTypeEnum {
static fromValue(value: ViewType): o.Expression { static fromValue(value: ViewType): o.Expression {
return createEnumExpression(Identifiers.ViewType, value); return createEnumExpression(Identifiers.ViewType, value);

View File

@ -6,183 +6,136 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {CompileDirectiveMetadata} from '../compile_metadata';
import {EventHandlerVars, convertActionBinding} from '../compiler_util/expression_converter'; import {EventHandlerVars, convertActionBinding} from '../compiler_util/expression_converter';
import {createInlineArray} from '../compiler_util/identifier_util';
import {DirectiveWrapperExpressions} from '../directive_wrapper_compiler';
import {MapWrapper} from '../facade/collection';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
import {identifierToken} from '../identifiers'; import {Identifiers, resolveIdentifier} from '../identifiers';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {BoundEventAst, DirectiveAst} from '../template_parser/template_ast'; import {BoundEventAst, DirectiveAst} from '../template_parser/template_ast';
import {CompileElement} from './compile_element'; import {CompileElement} from './compile_element';
import {CompileMethod} from './compile_method'; import {CompileMethod} from './compile_method';
import {ViewProperties} from './constants'; import {ViewProperties} from './constants';
import {getHandleEventMethodName} from './util';
export class CompileEventListener { export function bindOutputs(
private _method: CompileMethod; boundEvents: BoundEventAst[], directives: DirectiveAst[], compileElement: CompileElement,
private _hasComponentHostListener: boolean = false; bindToRenderer: boolean): boolean {
private _methodName: string; const usedEvents = collectEvents(boundEvents, directives);
private _eventParam: o.FnParam; if (!usedEvents.size) {
private _actionResultExprs: o.Expression[] = []; return false;
static getOrCreate(
compileElement: CompileElement, eventTarget: string, eventName: string, eventPhase: string,
targetEventListeners: CompileEventListener[]): CompileEventListener {
var listener = targetEventListeners.find(
listener => listener.eventTarget == eventTarget && listener.eventName == eventName &&
listener.eventPhase == eventPhase);
if (!listener) {
listener = new CompileEventListener(
compileElement, eventTarget, eventName, eventPhase, targetEventListeners.length);
targetEventListeners.push(listener);
}
return listener;
} }
if (bindToRenderer) {
get methodName() { return this._methodName; } subscribeToRenderEvents(usedEvents, compileElement);
get isAnimation() { return !!this.eventPhase; }
constructor(
public compileElement: CompileElement, public eventTarget: string, public eventName: string,
public eventPhase: string, listenerIndex: number) {
this._method = new CompileMethod(compileElement.view);
this._methodName =
`_handle_${sanitizeEventName(eventName)}_${compileElement.nodeIndex}_${listenerIndex}`;
this._eventParam = new o.FnParam(
EventHandlerVars.event.name,
o.importType(this.compileElement.view.genConfig.renderTypes.renderEvent));
} }
subscribeToDirectiveEvents(usedEvents, directives, compileElement);
generateHandleEventMethod(boundEvents, directives, compileElement);
return true;
}
addAction( function collectEvents(
hostEvent: BoundEventAst, directive: CompileDirectiveMetadata, boundEvents: BoundEventAst[], directives: DirectiveAst[]): Map<string, EventSummary> {
directiveInstance: o.Expression) { const usedEvents = new Map<string, EventSummary>();
if (isPresent(directive) && directive.isComponent) { boundEvents.forEach((event) => { usedEvents.set(event.fullName, event); });
this._hasComponentHostListener = true; directives.forEach((dirAst) => {
dirAst.hostEvents.forEach((event) => { usedEvents.set(event.fullName, event); });
});
return usedEvents;
}
function subscribeToRenderEvents(
usedEvents: Map<string, EventSummary>, compileElement: CompileElement) {
const eventAndTargetExprs: o.Expression[] = [];
usedEvents.forEach((event) => {
if (!event.phase) {
eventAndTargetExprs.push(o.literal(event.name), o.literal(event.target));
} }
this._method.resetDebugInfo(this.compileElement.nodeIndex, hostEvent); });
var context = directiveInstance || this.compileElement.view.componentContext; if (eventAndTargetExprs.length) {
const view = this.compileElement.view; const disposableVar = o.variable(`disposable_${compileElement.view.disposables.length}`);
compileElement.view.disposables.push(disposableVar);
compileElement.view.createMethod.addStmt(
disposableVar
.set(o.importExpr(resolveIdentifier(Identifiers.subscribeToRenderElement)).callFn([
ViewProperties.renderer, compileElement.renderNode,
createInlineArray(eventAndTargetExprs), handleEventClosure(compileElement)
]))
.toDeclStmt(o.FUNCTION_TYPE, [o.StmtModifier.Private]));
}
}
function subscribeToDirectiveEvents(
usedEvents: Map<string, EventSummary>, directives: DirectiveAst[],
compileElement: CompileElement) {
const usedEventNames = MapWrapper.keys(usedEvents);
directives.forEach((dirAst) => {
const dirWrapper = compileElement.directiveWrapperInstance.get(dirAst.directive.type.reference);
compileElement.view.createMethod.addStmts(DirectiveWrapperExpressions.subscribe(
dirAst.directive, dirAst.hostProperties, usedEventNames, dirWrapper,
handleEventClosure(compileElement)));
});
}
function generateHandleEventMethod(
boundEvents: BoundEventAst[], directives: DirectiveAst[], compileElement: CompileElement) {
const hasComponentHostListener =
directives.some((dirAst) => dirAst.hostEvents.some((event) => dirAst.directive.isComponent));
const markPathToRootStart =
hasComponentHostListener ? compileElement.appElement.prop('componentView') : o.THIS_EXPR;
const handleEventStmts = new CompileMethod(compileElement.view);
handleEventStmts.resetDebugInfo(compileElement.nodeIndex, compileElement.sourceAst);
handleEventStmts.push(markPathToRootStart.callMethod('markPathToRootAsCheckOnce', []).toStmt());
const eventNameVar = o.variable('eventName');
const resultVar = o.variable('result');
handleEventStmts.push(resultVar.set(o.literal(true)).toDeclStmt(o.BOOL_TYPE));
directives.forEach((dirAst, dirIdx) => {
const dirWrapper = compileElement.directiveWrapperInstance.get(dirAst.directive.type.reference);
if (dirAst.hostEvents.length > 0) {
handleEventStmts.push(
resultVar
.set(DirectiveWrapperExpressions
.handleEvent(
dirAst.hostEvents, dirWrapper, eventNameVar, EventHandlerVars.event)
.and(resultVar))
.toStmt());
}
});
boundEvents.forEach((renderEvent, renderEventIdx) => {
const evalResult = convertActionBinding( const evalResult = convertActionBinding(
view, directive ? null : view, context, hostEvent.handler, compileElement.view, compileElement.view, compileElement.view.componentContext,
`${this.compileElement.nodeIndex}_${this._actionResultExprs.length}`); renderEvent.handler, `sub_${renderEventIdx}`);
const trueStmts = evalResult.stmts;
if (evalResult.preventDefault) { if (evalResult.preventDefault) {
this._actionResultExprs.push(evalResult.preventDefault); trueStmts.push(resultVar.set(evalResult.preventDefault.and(resultVar)).toStmt());
} }
this._method.addStmts(evalResult.stmts); // TODO(tbosch): convert this into a `switch` once our OutputAst supports it.
} handleEventStmts.push(
new o.IfStmt(eventNameVar.equals(o.literal(renderEvent.fullName)), trueStmts));
finishMethod() {
var markPathToRootStart = this._hasComponentHostListener ?
this.compileElement.appElement.prop('componentView') :
o.THIS_EXPR;
var resultExpr: o.Expression = o.literal(true);
this._actionResultExprs.forEach((expr) => { resultExpr = resultExpr.and(expr); });
var stmts =
(<o.Statement[]>[markPathToRootStart.callMethod('markPathToRootAsCheckOnce', []).toStmt()])
.concat(this._method.finish())
.concat([new o.ReturnStatement(resultExpr)]);
// private is fine here as no child view will reference the event handler...
this.compileElement.view.methods.push(new o.ClassMethod(
this._methodName, [this._eventParam], stmts, o.BOOL_TYPE, [o.StmtModifier.Private]));
}
listenToRenderer() {
var listenExpr: o.Expression;
var eventListener = o.THIS_EXPR.callMethod(
'eventHandler',
[o.THIS_EXPR.prop(this._methodName).callMethod(o.BuiltinMethod.Bind, [o.THIS_EXPR])]);
if (isPresent(this.eventTarget)) {
listenExpr = ViewProperties.renderer.callMethod(
'listenGlobal', [o.literal(this.eventTarget), o.literal(this.eventName), eventListener]);
} else {
listenExpr = ViewProperties.renderer.callMethod(
'listen', [this.compileElement.renderNode, o.literal(this.eventName), eventListener]);
}
var disposable = o.variable(`disposable_${this.compileElement.view.disposables.length}`);
this.compileElement.view.disposables.push(disposable);
// private is fine here as no child view will reference the event handler...
this.compileElement.view.createMethod.addStmt(
disposable.set(listenExpr).toDeclStmt(o.FUNCTION_TYPE, [o.StmtModifier.Private]));
}
listenToAnimation(animationTransitionVar: o.ReadVarExpr): o.Statement {
const callbackMethod = this.eventPhase == 'start' ? 'onStart' : 'onDone';
return animationTransitionVar
.callMethod(
callbackMethod,
[o.THIS_EXPR.prop(this.methodName).callMethod(o.BuiltinMethod.Bind, [o.THIS_EXPR])])
.toStmt();
}
listenToDirective(directiveInstance: o.Expression, observablePropName: string) {
var subscription = o.variable(`subscription_${this.compileElement.view.subscriptions.length}`);
this.compileElement.view.subscriptions.push(subscription);
var eventListener = o.THIS_EXPR.callMethod(
'eventHandler',
[o.THIS_EXPR.prop(this._methodName).callMethod(o.BuiltinMethod.Bind, [o.THIS_EXPR])]);
this.compileElement.view.createMethod.addStmt(
subscription
.set(directiveInstance.prop(observablePropName)
.callMethod(o.BuiltinMethod.SubscribeObservable, [eventListener]))
.toDeclStmt(null, [o.StmtModifier.Final]));
}
}
export function collectEventListeners(
hostEvents: BoundEventAst[], dirs: DirectiveAst[],
compileElement: CompileElement): CompileEventListener[] {
const eventListeners: CompileEventListener[] = [];
hostEvents.forEach((hostEvent) => {
var listener = CompileEventListener.getOrCreate(
compileElement, hostEvent.target, hostEvent.name, hostEvent.phase, eventListeners);
listener.addAction(hostEvent, null, null);
}); });
dirs.forEach((directiveAst) => { handleEventStmts.push(new o.ReturnStatement(resultVar));
var directiveInstance = compileElement.view.methods.push(new o.ClassMethod(
compileElement.instances.get(identifierToken(directiveAst.directive.type).reference); getHandleEventMethodName(compileElement.nodeIndex),
directiveAst.hostEvents.forEach((hostEvent) => { [
var listener = CompileEventListener.getOrCreate( new o.FnParam(eventNameVar.name, o.STRING_TYPE),
compileElement, hostEvent.target, hostEvent.name, hostEvent.phase, eventListeners); new o.FnParam(EventHandlerVars.event.name, o.DYNAMIC_TYPE)
listener.addAction(hostEvent, directiveAst.directive, directiveInstance); ],
}); handleEventStmts.finish(), o.BOOL_TYPE));
});
eventListeners.forEach((listener) => listener.finishMethod());
return eventListeners;
} }
export function bindDirectiveOutputs( function handleEventClosure(compileElement: CompileElement) {
directiveAst: DirectiveAst, directiveInstance: o.Expression, const handleEventMethodName = getHandleEventMethodName(compileElement.nodeIndex);
eventListeners: CompileEventListener[]) { return o.THIS_EXPR.callMethod(
Object.keys(directiveAst.directive.outputs).forEach(observablePropName => { 'eventHandler',
const eventName = directiveAst.directive.outputs[observablePropName]; [o.THIS_EXPR.prop(handleEventMethodName).callMethod(o.BuiltinMethod.Bind, [o.THIS_EXPR])]);
eventListeners.filter(listener => listener.eventName == eventName).forEach((listener) => {
listener.listenToDirective(directiveInstance, observablePropName);
});
});
} }
export function bindRenderOutputs(eventListeners: CompileEventListener[]) { type EventSummary = {
eventListeners.forEach(listener => { name: string,
// the animation listeners are handled within property_binder.ts to target: string,
// allow them to be placed next to the animation factory statements phase: string
if (!listener.isAnimation) { }
listener.listenToRenderer();
}
});
}
function convertStmtIntoExpression(stmt: o.Statement): o.Expression {
if (stmt instanceof o.ExpressionStatement) {
return stmt.expr;
} else if (stmt instanceof o.ReturnStatement) {
return stmt.value;
}
return null;
}
function sanitizeEventName(name: string): string {
return name.replace(/[^a-zA-Z_]/g, '_');
}

View File

@ -7,16 +7,15 @@
*/ */
import {CompileDirectiveMetadata, CompilePipeMetadata} from '../compile_metadata'; import {CompileDirectiveMetadata, CompilePipeMetadata} from '../compile_metadata';
import {DirectiveWrapperExpressions} from '../directive_wrapper_compiler';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {LifecycleHooks} from '../private_import_core'; import {LifecycleHooks} from '../private_import_core';
import {DirectiveAst, ProviderAst} from '../template_parser/template_ast'; import {DirectiveAst, ProviderAst, ProviderAstType} from '../template_parser/template_ast';
import {CompileElement} from './compile_element'; import {CompileElement} from './compile_element';
import {CompileView} from './compile_view'; import {CompileView} from './compile_view';
import {DetectChangesVars} from './constants'; import {DetectChangesVars} from './constants';
var STATE_IS_NEVER_CHECKED = o.THIS_EXPR.prop('numberOfChecks').identical(new o.LiteralExpr(0)); var STATE_IS_NEVER_CHECKED = o.THIS_EXPR.prop('numberOfChecks').identical(new o.LiteralExpr(0));
var NOT_THROW_ON_CHANGES = o.not(DetectChangesVars.throwOnChange); var NOT_THROW_ON_CHANGES = o.not(DetectChangesVars.throwOnChange);
@ -56,11 +55,24 @@ export function bindDirectiveAfterViewLifecycleCallbacks(
} }
} }
export function bindDirectiveWrapperLifecycleCallbacks(
dir: DirectiveAst, directiveWrapperIntance: o.Expression, compileElement: CompileElement) {
compileElement.view.destroyMethod.addStmts(
DirectiveWrapperExpressions.ngOnDestroy(dir.directive, directiveWrapperIntance));
compileElement.view.detachMethod.addStmts(DirectiveWrapperExpressions.ngOnDetach(
dir.hostProperties, directiveWrapperIntance, o.THIS_EXPR,
compileElement.component ? compileElement.appElement.prop('componentView') : o.THIS_EXPR,
compileElement.renderNode));
}
export function bindInjectableDestroyLifecycleCallbacks( export function bindInjectableDestroyLifecycleCallbacks(
provider: ProviderAst, providerInstance: o.Expression, compileElement: CompileElement) { provider: ProviderAst, providerInstance: o.Expression, compileElement: CompileElement) {
var onDestroyMethod = compileElement.view.destroyMethod; var onDestroyMethod = compileElement.view.destroyMethod;
onDestroyMethod.resetDebugInfo(compileElement.nodeIndex, compileElement.sourceAst); onDestroyMethod.resetDebugInfo(compileElement.nodeIndex, compileElement.sourceAst);
if (provider.lifecycleHooks.indexOf(LifecycleHooks.OnDestroy) !== -1) { if (provider.providerType !== ProviderAstType.Directive &&
provider.providerType !== ProviderAstType.Component &&
provider.lifecycleHooks.indexOf(LifecycleHooks.OnDestroy) !== -1) {
onDestroyMethod.addStmt(providerInstance.callMethod('ngOnDestroy', []).toStmt()); onDestroyMethod.addStmt(providerInstance.callMethod('ngOnDestroy', []).toStmt());
} }
} }

View File

@ -11,21 +11,22 @@ import {SecurityContext} from '@angular/core';
import {createCheckBindingField, createCheckBindingStmt} from '../compiler_util/binding_util'; import {createCheckBindingField, createCheckBindingStmt} from '../compiler_util/binding_util';
import {ConvertPropertyBindingResult, convertPropertyBinding} from '../compiler_util/expression_converter'; import {ConvertPropertyBindingResult, convertPropertyBinding} from '../compiler_util/expression_converter';
import {createEnumExpression} from '../compiler_util/identifier_util'; import {createEnumExpression} from '../compiler_util/identifier_util';
import {writeToRenderer} from '../compiler_util/render_util'; import {triggerAnimation, writeToRenderer} from '../compiler_util/render_util';
import {DirectiveWrapperExpressions} from '../directive_wrapper_compiler';
import * as cdAst from '../expression_parser/ast'; import * as cdAst from '../expression_parser/ast';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
import {Identifiers, resolveIdentifier} from '../identifiers'; import {Identifiers, resolveIdentifier} from '../identifiers';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {EMPTY_STATE as EMPTY_ANIMATION_STATE, LifecycleHooks, isDefaultChangeDetectionStrategy} from '../private_import_core'; import {EMPTY_STATE as EMPTY_ANIMATION_STATE, LifecycleHooks, isDefaultChangeDetectionStrategy} from '../private_import_core';
import {ElementSchemaRegistry} from '../schema/element_schema_registry'; import {ElementSchemaRegistry} from '../schema/element_schema_registry';
import {BoundElementPropertyAst, BoundTextAst, DirectiveAst, PropertyBindingType} from '../template_parser/template_ast'; import {BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, PropertyBindingType} from '../template_parser/template_ast';
import {camelCaseToDashCase} from '../util'; import {camelCaseToDashCase} from '../util';
import {CompileElement, CompileNode} from './compile_element'; import {CompileElement, CompileNode} from './compile_element';
import {CompileMethod} from './compile_method'; import {CompileMethod} from './compile_method';
import {CompileView} from './compile_view'; import {CompileView} from './compile_view';
import {DetectChangesVars, ViewProperties} from './constants'; import {DetectChangesVars, ViewProperties} from './constants';
import {CompileEventListener} from './event_binder'; import {getHandleEventMethodName} from './util';
export function bindRenderText( export function bindRenderText(
boundText: BoundTextAst, compileNode: CompileNode, view: CompileView): void { boundText: BoundTextAst, compileNode: CompileNode, view: CompileView): void {
@ -44,118 +45,70 @@ export function bindRenderText(
.toStmt()])); .toStmt()]));
} }
function bindAndWriteToRenderer( export function bindRenderInputs(
boundProps: BoundElementPropertyAst[], context: o.Expression, compileElement: CompileElement, boundProps: BoundElementPropertyAst[], hasEvents: boolean, compileElement: CompileElement) {
isHostProp: boolean, eventListeners: CompileEventListener[]) {
var view = compileElement.view; var view = compileElement.view;
var renderNode = compileElement.renderNode; var renderNode = compileElement.renderNode;
boundProps.forEach((boundProp) => { boundProps.forEach((boundProp) => {
const bindingField = createCheckBindingField(view); const bindingField = createCheckBindingField(view);
view.detectChangesRenderPropertiesMethod.resetDebugInfo(compileElement.nodeIndex, boundProp); view.detectChangesRenderPropertiesMethod.resetDebugInfo(compileElement.nodeIndex, boundProp);
const evalResult = convertPropertyBinding( const evalResult = convertPropertyBinding(
view, isHostProp ? null : view, context, boundProp.value, bindingField.bindingId); view, view, compileElement.view.componentContext, boundProp.value, bindingField.bindingId);
var updateStmts: o.Statement[] = []; var checkBindingStmts: o.Statement[] = [];
var compileMethod = view.detectChangesRenderPropertiesMethod; var compileMethod = view.detectChangesRenderPropertiesMethod;
switch (boundProp.type) { switch (boundProp.type) {
case PropertyBindingType.Property: case PropertyBindingType.Property:
case PropertyBindingType.Attribute: case PropertyBindingType.Attribute:
case PropertyBindingType.Class: case PropertyBindingType.Class:
case PropertyBindingType.Style: case PropertyBindingType.Style:
updateStmts.push(...writeToRenderer( checkBindingStmts.push(...writeToRenderer(
o.THIS_EXPR, boundProp, renderNode, evalResult.currValExpr, o.THIS_EXPR, boundProp, renderNode, evalResult.currValExpr,
view.genConfig.logBindingUpdate)); view.genConfig.logBindingUpdate));
break; break;
case PropertyBindingType.Animation: case PropertyBindingType.Animation:
compileMethod = view.animationBindingsMethod; compileMethod = view.animationBindingsMethod;
const detachStmts: o.Statement[] = []; const {updateStmts, detachStmts} = triggerAnimation(
o.THIS_EXPR, o.THIS_EXPR, boundProp,
const animationName = boundProp.name; (hasEvents ? o.THIS_EXPR.prop(getHandleEventMethodName(compileElement.nodeIndex)) :
const targetViewExpr: o.Expression = o.importExpr(resolveIdentifier(Identifiers.noop)))
isHostProp ? compileElement.appElement.prop('componentView') : o.THIS_EXPR; .callMethod(o.BuiltinMethod.Bind, [o.THIS_EXPR]),
compileElement.renderNode, evalResult.currValExpr, bindingField.expression);
const animationFnExpr = checkBindingStmts.push(...updateStmts);
targetViewExpr.prop('componentType').prop('animations').key(o.literal(animationName));
// it's important to normalize the void value as `void` explicitly
// so that the styles data can be obtained from the stringmap
const emptyStateValue = o.literal(EMPTY_ANIMATION_STATE);
const unitializedValue = o.importExpr(resolveIdentifier(Identifiers.UNINITIALIZED));
const animationTransitionVar = o.variable('animationTransition_' + animationName);
updateStmts.push(animationTransitionVar
.set(animationFnExpr.callFn([
o.THIS_EXPR, renderNode,
bindingField.expression.equals(unitializedValue)
.conditional(emptyStateValue, bindingField.expression),
evalResult.currValExpr.equals(unitializedValue)
.conditional(emptyStateValue, evalResult.currValExpr)
]))
.toDeclStmt());
detachStmts.push(
animationTransitionVar
.set(animationFnExpr.callFn(
[o.THIS_EXPR, renderNode, bindingField.expression, emptyStateValue]))
.toDeclStmt());
eventListeners.forEach(listener => {
if (listener.isAnimation && listener.eventName === animationName) {
let animationStmt = listener.listenToAnimation(animationTransitionVar);
updateStmts.push(animationStmt);
detachStmts.push(animationStmt);
}
});
view.detachMethod.addStmts(detachStmts); view.detachMethod.addStmts(detachStmts);
break; break;
} }
compileMethod.addStmts(createCheckBindingStmt( compileMethod.addStmts(createCheckBindingStmt(
evalResult, bindingField.expression, DetectChangesVars.throwOnChange, updateStmts)); evalResult, bindingField.expression, DetectChangesVars.throwOnChange, checkBindingStmts));
}); });
} }
export function bindRenderInputs(
boundProps: BoundElementPropertyAst[], compileElement: CompileElement,
eventListeners: CompileEventListener[]): void {
bindAndWriteToRenderer(
boundProps, compileElement.view.componentContext, compileElement, false, eventListeners);
}
export function bindDirectiveHostProps( export function bindDirectiveHostProps(
directiveAst: DirectiveAst, directiveWrapperInstance: o.Expression, directiveAst: DirectiveAst, directiveWrapperInstance: o.Expression,
compileElement: CompileElement, eventListeners: CompileEventListener[], elementName: string, compileElement: CompileElement, elementName: string,
schemaRegistry: ElementSchemaRegistry): void { schemaRegistry: ElementSchemaRegistry): void {
// host properties are change detected by the DirectiveWrappers,
// except for the animation properties as they need close integration with animation events
// and DirectiveWrappers don't support
// event listeners right now.
bindAndWriteToRenderer(
directiveAst.hostProperties.filter(boundProp => boundProp.isAnimation),
directiveWrapperInstance.prop('context'), compileElement, true, eventListeners);
const methodArgs: o.Expression[] =
[o.THIS_EXPR, compileElement.renderNode, DetectChangesVars.throwOnChange];
// We need to provide the SecurityContext for properties that could need sanitization. // We need to provide the SecurityContext for properties that could need sanitization.
directiveAst.hostProperties.filter(boundProp => boundProp.needsRuntimeSecurityContext) const runtimeSecurityCtxExprs =
.forEach((boundProp) => { directiveAst.hostProperties.filter(boundProp => boundProp.needsRuntimeSecurityContext)
let ctx: SecurityContext; .map((boundProp) => {
switch (boundProp.type) { let ctx: SecurityContext;
case PropertyBindingType.Property: switch (boundProp.type) {
ctx = schemaRegistry.securityContext(elementName, boundProp.name, false); case PropertyBindingType.Property:
break; ctx = schemaRegistry.securityContext(elementName, boundProp.name, false);
case PropertyBindingType.Attribute: break;
ctx = schemaRegistry.securityContext(elementName, boundProp.name, true); case PropertyBindingType.Attribute:
break; ctx = schemaRegistry.securityContext(elementName, boundProp.name, true);
default: break;
throw new Error( default:
`Illegal state: Only property / attribute bindings can have an unknown security context! Binding ${boundProp.name}`); throw new Error(
} `Illegal state: Only property / attribute bindings can have an unknown security context! Binding ${boundProp.name}`);
methodArgs.push(createEnumExpression(Identifiers.SecurityContext, ctx)); }
}); return createEnumExpression(Identifiers.SecurityContext, ctx);
compileElement.view.detectChangesRenderPropertiesMethod.addStmt( });
directiveWrapperInstance.callMethod('detectChangesInHostProps', methodArgs).toStmt()); compileElement.view.detectChangesRenderPropertiesMethod.addStmts(
DirectiveWrapperExpressions.checkHost(
directiveAst.hostProperties, directiveWrapperInstance, o.THIS_EXPR,
compileElement.component ? compileElement.appElement.prop('componentView') : o.THIS_EXPR,
compileElement.renderNode, DetectChangesVars.throwOnChange, runtimeSecurityCtxExprs));
} }
export function bindDirectiveInputs( export function bindDirectiveInputs(
@ -187,9 +140,9 @@ export function bindDirectiveInputs(
}); });
var isOnPushComp = directiveAst.directive.isComponent && var isOnPushComp = directiveAst.directive.isComponent &&
!isDefaultChangeDetectionStrategy(directiveAst.directive.changeDetection); !isDefaultChangeDetectionStrategy(directiveAst.directive.changeDetection);
let directiveDetectChangesExpr = directiveWrapperInstance.callMethod( let directiveDetectChangesExpr = DirectiveWrapperExpressions.ngDoCheck(
'detectChangesInInputProps', directiveWrapperInstance, o.THIS_EXPR, compileElement.renderNode,
[o.THIS_EXPR, compileElement.renderNode, DetectChangesVars.throwOnChange]); DetectChangesVars.throwOnChange);
const directiveDetectChangesStmt = isOnPushComp ? const directiveDetectChangesStmt = isOnPushComp ?
new o.IfStmt(directiveDetectChangesExpr, [compileElement.appElement.prop('componentView') new o.IfStmt(directiveDetectChangesExpr, [compileElement.appElement.prop('componentView')
.callMethod('markAsCheckOnce', []) .callMethod('markAsCheckOnce', [])

View File

@ -91,3 +91,7 @@ export function createFlatArray(expressions: o.Expression[]): o.Expression {
} }
return result; return result;
} }
export function getHandleEventMethodName(elementIndex: number): string {
return `handleEvent_${elementIndex}`;
}

View File

@ -11,8 +11,8 @@ import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventA
import {CompileElement} from './compile_element'; import {CompileElement} from './compile_element';
import {CompileView} from './compile_view'; import {CompileView} from './compile_view';
import {CompileEventListener, bindDirectiveOutputs, bindRenderOutputs, collectEventListeners} from './event_binder'; import {bindOutputs} from './event_binder';
import {bindDirectiveAfterContentLifecycleCallbacks, bindDirectiveAfterViewLifecycleCallbacks, bindInjectableDestroyLifecycleCallbacks, bindPipeDestroyLifecycleCallbacks} from './lifecycle_binder'; import {bindDirectiveAfterContentLifecycleCallbacks, bindDirectiveAfterViewLifecycleCallbacks, bindDirectiveWrapperLifecycleCallbacks, bindInjectableDestroyLifecycleCallbacks, bindPipeDestroyLifecycleCallbacks} from './lifecycle_binder';
import {bindDirectiveHostProps, bindDirectiveInputs, bindRenderInputs, bindRenderText} from './property_binder'; import {bindDirectiveHostProps, bindDirectiveInputs, bindRenderInputs, bindRenderText} from './property_binder';
export function bindView( export function bindView(
@ -42,32 +42,29 @@ class ViewBinderVisitor implements TemplateAstVisitor {
visitElement(ast: ElementAst, parent: CompileElement): any { visitElement(ast: ElementAst, parent: CompileElement): any {
var compileElement = <CompileElement>this.view.nodes[this._nodeIndex++]; var compileElement = <CompileElement>this.view.nodes[this._nodeIndex++];
var eventListeners: CompileEventListener[] = []; const hasEvents = bindOutputs(ast.outputs, ast.directives, compileElement, true);
collectEventListeners(ast.outputs, ast.directives, compileElement).forEach(entry => { bindRenderInputs(ast.inputs, hasEvents, compileElement);
eventListeners.push(entry);
});
bindRenderInputs(ast.inputs, compileElement, eventListeners);
bindRenderOutputs(eventListeners);
ast.directives.forEach((directiveAst, dirIndex) => { ast.directives.forEach((directiveAst, dirIndex) => {
var directiveInstance = compileElement.instances.get(directiveAst.directive.type.reference); var directiveInstance = compileElement.instances.get(directiveAst.directive.type.reference);
var directiveWrapperInstance = var directiveWrapperInstance =
compileElement.directiveWrapperInstance.get(directiveAst.directive.type.reference); compileElement.directiveWrapperInstance.get(directiveAst.directive.type.reference);
bindDirectiveInputs(directiveAst, directiveWrapperInstance, dirIndex, compileElement); bindDirectiveInputs(directiveAst, directiveWrapperInstance, dirIndex, compileElement);
bindDirectiveHostProps( bindDirectiveHostProps(
directiveAst, directiveWrapperInstance, compileElement, eventListeners, ast.name, directiveAst, directiveWrapperInstance, compileElement, ast.name, this._schemaRegistry);
this._schemaRegistry);
bindDirectiveOutputs(directiveAst, directiveInstance, eventListeners);
}); });
templateVisitAll(this, ast.children, compileElement); templateVisitAll(this, ast.children, compileElement);
// afterContent and afterView lifecycles need to be called bottom up // afterContent and afterView lifecycles need to be called bottom up
// so that children are notified before parents // so that children are notified before parents
ast.directives.forEach((directiveAst) => { ast.directives.forEach((directiveAst) => {
var directiveInstance = compileElement.instances.get(directiveAst.directive.type.reference); var directiveInstance = compileElement.instances.get(directiveAst.directive.type.reference);
var directiveWrapperInstance =
compileElement.directiveWrapperInstance.get(directiveAst.directive.type.reference);
bindDirectiveAfterContentLifecycleCallbacks( bindDirectiveAfterContentLifecycleCallbacks(
directiveAst.directive, directiveInstance, compileElement); directiveAst.directive, directiveInstance, compileElement);
bindDirectiveAfterViewLifecycleCallbacks( bindDirectiveAfterViewLifecycleCallbacks(
directiveAst.directive, directiveInstance, compileElement); directiveAst.directive, directiveInstance, compileElement);
bindDirectiveWrapperLifecycleCallbacks(
directiveAst, directiveWrapperInstance, compileElement);
}); });
ast.providers.forEach((providerAst) => { ast.providers.forEach((providerAst) => {
var providerInstance = compileElement.instances.get(providerAst.token.reference); var providerInstance = compileElement.instances.get(providerAst.token.reference);
@ -78,18 +75,19 @@ class ViewBinderVisitor implements TemplateAstVisitor {
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, parent: CompileElement): any { visitEmbeddedTemplate(ast: EmbeddedTemplateAst, parent: CompileElement): any {
var compileElement = <CompileElement>this.view.nodes[this._nodeIndex++]; var compileElement = <CompileElement>this.view.nodes[this._nodeIndex++];
var eventListeners = collectEventListeners(ast.outputs, ast.directives, compileElement); bindOutputs(ast.outputs, ast.directives, compileElement, false);
ast.directives.forEach((directiveAst, dirIndex) => { ast.directives.forEach((directiveAst, dirIndex) => {
var directiveInstance = compileElement.instances.get(directiveAst.directive.type.reference); var directiveInstance = compileElement.instances.get(directiveAst.directive.type.reference);
var directiveWrapperInstance = var directiveWrapperInstance =
compileElement.directiveWrapperInstance.get(directiveAst.directive.type.reference); compileElement.directiveWrapperInstance.get(directiveAst.directive.type.reference);
bindDirectiveInputs(directiveAst, directiveWrapperInstance, dirIndex, compileElement); bindDirectiveInputs(directiveAst, directiveWrapperInstance, dirIndex, compileElement);
bindDirectiveOutputs(directiveAst, directiveInstance, eventListeners);
bindDirectiveAfterContentLifecycleCallbacks( bindDirectiveAfterContentLifecycleCallbacks(
directiveAst.directive, directiveInstance, compileElement); directiveAst.directive, directiveInstance, compileElement);
bindDirectiveAfterViewLifecycleCallbacks( bindDirectiveAfterViewLifecycleCallbacks(
directiveAst.directive, directiveInstance, compileElement); directiveAst.directive, directiveInstance, compileElement);
bindDirectiveWrapperLifecycleCallbacks(
directiveAst, directiveWrapperInstance, compileElement);
}); });
ast.providers.forEach((providerAst) => { ast.providers.forEach((providerAst) => {
var providerInstance = compileElement.instances.get(providerAst.token.reference); var providerInstance = compileElement.instances.get(providerAst.token.reference);

View File

@ -285,7 +285,9 @@ export abstract class AppView<T> {
} }
} }
eventHandler<E, R>(cb: (event?: E) => R): (event?: E) => R { return cb; } eventHandler<E, R>(cb: (eventName: string, event?: E) => R): (eventName: string, event?: E) => R {
return cb;
}
throwDestroyedError(details: string): void { throw new ViewDestroyedError(details); } throwDestroyedError(details: string): void { throw new ViewDestroyedError(details); }
} }
@ -368,12 +370,12 @@ export class DebugAppView<T> extends AppView<T> {
} }
} }
eventHandler<E, R>(cb: (event?: E) => R): (event?: E) => R { eventHandler<E, R>(cb: (eventName: string, event?: E) => R): (eventName: string, event?: E) => R {
var superHandler = super.eventHandler(cb); var superHandler = super.eventHandler(cb);
return (event?: any) => { return (eventName: string, event?: any) => {
this._resetDebug(); this._resetDebug();
try { try {
return superHandler(event); return superHandler(eventName, event);
} catch (e) { } catch (e) {
this._rethrowWithContext(e); this._rethrowWithContext(e);
throw e; throw e;

View File

@ -400,14 +400,59 @@ export function selectOrCreateRenderHostElement(
return hostElement; return hostElement;
} }
export function subscribeToRenderElement(
renderer: Renderer, element: any, eventNamesAndTargets: InlineArray<string>,
listener: (eventName: string, event: any) => any) {
const disposables = createEmptyInlineArray(eventNamesAndTargets.length / 2);
for (var i = 0; i < eventNamesAndTargets.length; i += 2) {
const eventName = eventNamesAndTargets.get(i);
const eventTarget = eventNamesAndTargets.get(i + 1);
let disposable: Function;
if (eventTarget) {
disposable = renderer.listenGlobal(
eventTarget, eventName, listener.bind(null, `${eventTarget}:${eventName}`));
} else {
disposable = renderer.listen(element, eventName, listener.bind(null, eventName));
}
disposables.set(i / 2, disposable);
}
return disposeInlineArray.bind(null, disposables);
}
function disposeInlineArray(disposables: InlineArray<Function>) {
for (var i = 0; i < disposables.length; i++) {
disposables.get(i)();
}
}
export function noop() {}
export interface InlineArray<T> { export interface InlineArray<T> {
length: number; length: number;
get(index: number): T; get(index: number): T;
set(index: number, value: T): void;
}
function createEmptyInlineArray<T>(length: number): InlineArray<T> {
let ctor: any;
if (length <= 2) {
ctor = InlineArray2;
} else if (length <= 4) {
ctor = InlineArray4;
} else if (length <= 8) {
ctor = InlineArray8;
} else if (length <= 16) {
ctor = InlineArray16;
} else {
ctor = InlineArrayDynamic;
}
return new ctor(length);
} }
class InlineArray0 implements InlineArray<any> { class InlineArray0 implements InlineArray<any> {
length = 0; length = 0;
get(index: number): any { return undefined; } get(index: number): any { return undefined; }
set(index: number, value: any): void {}
} }
export class InlineArray2<T> implements InlineArray<T> { export class InlineArray2<T> implements InlineArray<T> {
@ -422,6 +467,16 @@ export class InlineArray2<T> implements InlineArray<T> {
return undefined; return undefined;
} }
} }
set(index: number, value: T) {
switch (index) {
case 0:
this._v0 = value;
break;
case 1:
this._v1 = value;
break;
}
}
} }
export class InlineArray4<T> implements InlineArray<T> { export class InlineArray4<T> implements InlineArray<T> {
@ -441,6 +496,22 @@ export class InlineArray4<T> implements InlineArray<T> {
return undefined; return undefined;
} }
} }
set(index: number, value: T) {
switch (index) {
case 0:
this._v0 = value;
break;
case 1:
this._v1 = value;
break;
case 2:
this._v2 = value;
break;
case 3:
this._v3 = value;
break;
}
}
} }
export class InlineArray8<T> implements InlineArray<T> { export class InlineArray8<T> implements InlineArray<T> {
@ -469,6 +540,34 @@ export class InlineArray8<T> implements InlineArray<T> {
return undefined; return undefined;
} }
} }
set(index: number, value: T) {
switch (index) {
case 0:
this._v0 = value;
break;
case 1:
this._v1 = value;
break;
case 2:
this._v2 = value;
break;
case 3:
this._v3 = value;
break;
case 4:
this._v4 = value;
break;
case 5:
this._v5 = value;
break;
case 6:
this._v6 = value;
break;
case 7:
this._v7 = value;
break;
}
}
} }
export class InlineArray16<T> implements InlineArray<T> { export class InlineArray16<T> implements InlineArray<T> {
@ -515,6 +614,58 @@ export class InlineArray16<T> implements InlineArray<T> {
return undefined; return undefined;
} }
} }
set(index: number, value: T) {
switch (index) {
case 0:
this._v0 = value;
break;
case 1:
this._v1 = value;
break;
case 2:
this._v2 = value;
break;
case 3:
this._v3 = value;
break;
case 4:
this._v4 = value;
break;
case 5:
this._v5 = value;
break;
case 6:
this._v6 = value;
break;
case 7:
this._v7 = value;
break;
case 8:
this._v8 = value;
break;
case 9:
this._v9 = value;
break;
case 10:
this._v10 = value;
break;
case 11:
this._v11 = value;
break;
case 12:
this._v12 = value;
break;
case 13:
this._v13 = value;
break;
case 14:
this._v14 = value;
break;
case 15:
this._v15 = value;
break;
}
}
} }
export class InlineArrayDynamic<T> implements InlineArray<T> { export class InlineArrayDynamic<T> implements InlineArray<T> {
@ -524,6 +675,7 @@ export class InlineArrayDynamic<T> implements InlineArray<T> {
constructor(public length: number, ...values: any[]) { this._values = values; } constructor(public length: number, ...values: any[]) { this._values = values; }
get(index: number) { return this._values[index]; } get(index: number) { return this._values[index]; }
set(index: number, value: T) { this._values[index] = value; }
} }
export const EMPTY_INLINE_ARRAY: InlineArray<any> = new InlineArray0(); export const EMPTY_INLINE_ARRAY: InlineArray<any> = new InlineArray0();