refactor(compiler): generate host listeners in DirectiveWrappers
Part of #11683
This commit is contained in:
parent
a664aba2c9
commit
32feb8a532
|
@ -71,7 +71,7 @@ export function convertPropertyBinding(
|
|||
}
|
||||
|
||||
export class ConvertActionBindingResult {
|
||||
constructor(public stmts: o.Statement[], public preventDefault: o.Expression) {}
|
||||
constructor(public stmts: o.Statement[], public preventDefault: o.ReadVarExpr) {}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -10,7 +10,8 @@ import {SecurityContext} from '@angular/core';
|
|||
import {isPresent} from '../facade/lang';
|
||||
import {Identifiers, resolveIdentifier} from '../identifiers';
|
||||
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';
|
||||
|
||||
|
@ -87,3 +88,59 @@ function sanitizedValue(
|
|||
let args = [securityContextExpression, renderValue];
|
||||
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};
|
||||
}
|
||||
|
|
|
@ -10,8 +10,8 @@ import {Injectable} from '@angular/core';
|
|||
|
||||
import {CompileDirectiveMetadata, CompileIdentifierMetadata} from './compile_metadata';
|
||||
import {createCheckBindingField, createCheckBindingStmt} from './compiler_util/binding_util';
|
||||
import {convertPropertyBinding} from './compiler_util/expression_converter';
|
||||
import {writeToRenderer} from './compiler_util/render_util';
|
||||
import {EventHandlerVars, convertActionBinding, convertPropertyBinding} from './compiler_util/expression_converter';
|
||||
import {triggerAnimation, writeToRenderer} from './compiler_util/render_util';
|
||||
import {CompilerConfig} from './config';
|
||||
import {Parser} from './expression_parser/parser';
|
||||
import {Identifiers, resolveIdentifier} from './identifiers';
|
||||
|
@ -31,12 +31,15 @@ export class DirectiveWrapperCompileResult {
|
|||
const CONTEXT_FIELD_NAME = 'context';
|
||||
const CHANGES_FIELD_NAME = 'changes';
|
||||
const CHANGED_FIELD_NAME = 'changed';
|
||||
const EVENT_HANDLER_FIELD_NAME = 'eventHandler';
|
||||
|
||||
const CURR_VALUE_VAR = o.variable('currValue');
|
||||
const THROW_ON_CHANGE_VAR = o.variable('throwOnChange');
|
||||
const FORCE_UPDATE_VAR = o.variable('forceUpdate');
|
||||
const VIEW_VAR = o.variable('view');
|
||||
const COMPONENT_VIEW_VAR = o.variable('componentView');
|
||||
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();
|
||||
|
||||
|
@ -57,22 +60,17 @@ export class DirectiveWrapperCompiler {
|
|||
private _schemaRegistry: ElementSchemaRegistry, private _console: Console) {}
|
||||
|
||||
compile(dirMeta: CompileDirectiveMetadata): DirectiveWrapperCompileResult {
|
||||
const hostParseResult = parseHostBindings(dirMeta, this._exprParser, this._schemaRegistry);
|
||||
reportParseErrors(hostParseResult.errors, this._console);
|
||||
|
||||
const builder = new DirectiveWrapperBuilder(this.compilerConfig, dirMeta);
|
||||
Object.keys(dirMeta.inputs).forEach((inputFieldName) => {
|
||||
addCheckInputMethod(inputFieldName, builder);
|
||||
});
|
||||
addDetectChangesInInputPropsMethod(builder);
|
||||
|
||||
const hostParseResult = parseHostBindings(dirMeta, this._exprParser, this._schemaRegistry);
|
||||
reportParseErrors(hostParseResult.errors, this._console);
|
||||
// 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!
|
||||
addNgDoCheckMethod(builder);
|
||||
addCheckHostMethod(hostParseResult.hostProps, builder);
|
||||
addHandleEventMethod(hostParseResult.hostListeners, builder);
|
||||
addSubscribeMethod(dirMeta, builder);
|
||||
|
||||
const classStmt = builder.build();
|
||||
return new DirectiveWrapperCompileResult([classStmt], classStmt.name);
|
||||
|
@ -84,11 +82,14 @@ class DirectiveWrapperBuilder implements ClassBuilder {
|
|||
getters: o.ClassGetter[] = [];
|
||||
methods: o.ClassMethod[] = [];
|
||||
ctorStmts: o.Statement[] = [];
|
||||
detachStmts: o.Statement[] = [];
|
||||
destroyStmts: o.Statement[] = [];
|
||||
|
||||
genChanges: boolean;
|
||||
ngOnChanges: boolean;
|
||||
ngOnInit: boolean;
|
||||
ngDoCheck: boolean;
|
||||
ngOnDestroy: boolean;
|
||||
|
||||
constructor(public compilerConfig: CompilerConfig, public dirMeta: CompileDirectiveMetadata) {
|
||||
const dirLifecycleHooks = dirMeta.type.lifecycleHooks;
|
||||
|
@ -97,6 +98,11 @@ class DirectiveWrapperBuilder implements ClassBuilder {
|
|||
this.ngOnChanges = dirLifecycleHooks.indexOf(LifecycleHooks.OnChanges) !== -1;
|
||||
this.ngOnInit = dirLifecycleHooks.indexOf(LifecycleHooks.OnInit) !== -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 {
|
||||
|
@ -105,7 +111,25 @@ class DirectiveWrapperBuilder implements ClassBuilder {
|
|||
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[] = [
|
||||
new o.ClassField(EVENT_HANDLER_FIELD_NAME, o.FUNCTION_TYPE),
|
||||
new o.ClassField(CONTEXT_FIELD_NAME, o.importType(this.dirMeta.type)),
|
||||
new o.ClassField(CHANGED_FIELD_NAME, o.BOOL_TYPE),
|
||||
];
|
||||
|
@ -125,12 +149,12 @@ class DirectiveWrapperBuilder implements ClassBuilder {
|
|||
return createClassStmt({
|
||||
name: DirectiveWrapperCompiler.dirWrapperClassName(this.dirMeta.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 stmts: o.Statement[] = [
|
||||
changedVar.set(o.THIS_EXPR.prop(CHANGED_FIELD_NAME)).toDeclStmt(),
|
||||
|
@ -170,7 +194,7 @@ function addDetectChangesInInputPropsMethod(builder: DirectiveWrapperBuilder) {
|
|||
stmts.push(new o.ReturnStatement(changedVar));
|
||||
|
||||
builder.methods.push(new o.ClassMethod(
|
||||
'detectChangesInInputProps',
|
||||
'ngDoCheck',
|
||||
[
|
||||
new o.FnParam(
|
||||
VIEW_VAR.name, o.importType(resolveIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
|
||||
|
@ -207,16 +231,19 @@ function addCheckInputMethod(input: string, builder: DirectiveWrapperBuilder) {
|
|||
methodBody));
|
||||
}
|
||||
|
||||
function addDetectChangesInHostPropsMethod(
|
||||
function addCheckHostMethod(
|
||||
hostProps: BoundElementPropertyAst[], builder: DirectiveWrapperBuilder) {
|
||||
const stmts: o.Statement[] = [];
|
||||
const methodParams: o.FnParam[] = [
|
||||
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),
|
||||
new o.FnParam(THROW_ON_CHANGE_VAR.name, o.BOOL_TYPE),
|
||||
];
|
||||
hostProps.forEach((hostProp) => {
|
||||
hostProps.forEach((hostProp, hostPropIdx) => {
|
||||
const field = createCheckBindingField(builder);
|
||||
const evalResult = convertPropertyBinding(
|
||||
builder, null, o.THIS_EXPR.prop(CONTEXT_FIELD_NAME), hostProp.value, field.bindingId);
|
||||
|
@ -229,13 +256,80 @@ function addDetectChangesInHostPropsMethod(
|
|||
methodParams.push(new o.FnParam(
|
||||
securityContextExpr.name, o.importType(resolveIdentifier(Identifiers.SecurityContext))));
|
||||
}
|
||||
stmts.push(...createCheckBindingStmt(
|
||||
evalResult, field.expression, THROW_ON_CHANGE_VAR,
|
||||
writeToRenderer(
|
||||
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)));
|
||||
builder.compilerConfig.logBindingUpdate, securityContextExpr);
|
||||
}
|
||||
|
||||
stmts.push(...createCheckBindingStmt(
|
||||
evalResult, field.expression, THROW_ON_CHANGE_VAR, checkBindingStmts));
|
||||
});
|
||||
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 {
|
||||
|
@ -275,3 +369,84 @@ function reportParseErrors(parseErrors: ParseError[], console: Console) {
|
|||
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]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -313,6 +313,13 @@ export class Identifiers {
|
|||
moduleUrl: VIEW_UTILS_MODULE_URL,
|
||||
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 {
|
||||
|
|
|
@ -152,13 +152,13 @@ class StatementInterpreter implements o.StatementVisitor, o.ExpressionVisitor {
|
|||
if (isPresent(expr.builtin)) {
|
||||
switch (expr.builtin) {
|
||||
case o.BuiltinMethod.ConcatArray:
|
||||
result = receiver.concat(args[0]);
|
||||
result = receiver.concat(...args);
|
||||
break;
|
||||
case o.BuiltinMethod.SubscribeObservable:
|
||||
result = receiver.subscribe({next: args[0]});
|
||||
break;
|
||||
case o.BuiltinMethod.Bind:
|
||||
result = receiver.bind(args[0]);
|
||||
result = receiver.bind(...args);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown builtin method ${expr.builtin}`);
|
||||
|
|
|
@ -76,19 +76,23 @@ export class BoundElementPropertyAst implements TemplateAst {
|
|||
* `(@trigger.phase)="callback($event)"`).
|
||||
*/
|
||||
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(
|
||||
public name: string, public target: string, public phase: string, public handler: AST,
|
||||
public sourceSpan: ParseSourceSpan) {}
|
||||
visit(visitor: TemplateAstVisitor, context: any): any {
|
||||
return visitor.visitEvent(this, context);
|
||||
}
|
||||
get fullName() {
|
||||
if (this.target) {
|
||||
return `${this.target}:${this.name}`;
|
||||
} else {
|
||||
return this.name;
|
||||
}
|
||||
}
|
||||
get fullName() { return BoundEventAst.calcFullName(this.name, this.target, this.phase); }
|
||||
get isAnimation(): boolean { return !!this.phase; }
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileIdentifierMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileTokenMetadata} from '../compile_metadata';
|
||||
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 {isPresent} from '../facade/lang';
|
||||
import {Identifiers, identifierToken, resolveIdentifier, resolveIdentifierToken} from '../identifiers';
|
||||
|
@ -188,8 +188,7 @@ export class CompileElement extends CompileNode {
|
|||
{name: DirectiveWrapperCompiler.dirWrapperClassName(provider.useClass)});
|
||||
this._targetDependencies.push(
|
||||
new DirectiveWrapperDependency(provider.useClass, directiveWrapperIdentifier));
|
||||
return o.importExpr(directiveWrapperIdentifier)
|
||||
.instantiate(depsExpr, o.importType(directiveWrapperIdentifier));
|
||||
return DirectiveWrapperExpressions.create(directiveWrapperIdentifier, depsExpr);
|
||||
} else {
|
||||
return o.importExpr(provider.useClass)
|
||||
.instantiate(depsExpr, o.importType(provider.useClass));
|
||||
|
@ -204,7 +203,8 @@ export class CompileElement extends CompileNode {
|
|||
resolvedProvider.eager, this);
|
||||
if (isDirectiveWrapper) {
|
||||
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 {
|
||||
this.instances.set(resolvedProvider.token.reference, instance);
|
||||
}
|
||||
|
|
|
@ -8,16 +8,11 @@
|
|||
|
||||
import {ChangeDetectionStrategy, ViewEncapsulation} from '@angular/core';
|
||||
|
||||
import {CompileIdentifierMetadata} from '../compile_metadata';
|
||||
import {createEnumExpression} from '../compiler_util/identifier_util';
|
||||
import {Identifiers, resolveEnumIdentifier} from '../identifiers';
|
||||
import {Identifiers} from '../identifiers';
|
||||
import * as o from '../output/output_ast';
|
||||
import {ChangeDetectorStatus, ViewType} from '../private_import_core';
|
||||
|
||||
function _enumExpression(classIdentifier: CompileIdentifierMetadata, name: string): o.Expression {
|
||||
return o.importExpr(resolveEnumIdentifier(classIdentifier, name));
|
||||
}
|
||||
|
||||
export class ViewTypeEnum {
|
||||
static fromValue(value: ViewType): o.Expression {
|
||||
return createEnumExpression(Identifiers.ViewType, value);
|
||||
|
|
|
@ -6,183 +6,136 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {CompileDirectiveMetadata} from '../compile_metadata';
|
||||
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 {identifierToken} from '../identifiers';
|
||||
import {Identifiers, resolveIdentifier} from '../identifiers';
|
||||
import * as o from '../output/output_ast';
|
||||
import {BoundEventAst, DirectiveAst} from '../template_parser/template_ast';
|
||||
|
||||
import {CompileElement} from './compile_element';
|
||||
import {CompileMethod} from './compile_method';
|
||||
import {ViewProperties} from './constants';
|
||||
import {getHandleEventMethodName} from './util';
|
||||
|
||||
export class CompileEventListener {
|
||||
private _method: CompileMethod;
|
||||
private _hasComponentHostListener: boolean = false;
|
||||
private _methodName: string;
|
||||
private _eventParam: o.FnParam;
|
||||
private _actionResultExprs: o.Expression[] = [];
|
||||
|
||||
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);
|
||||
export function bindOutputs(
|
||||
boundEvents: BoundEventAst[], directives: DirectiveAst[], compileElement: CompileElement,
|
||||
bindToRenderer: boolean): boolean {
|
||||
const usedEvents = collectEvents(boundEvents, directives);
|
||||
if (!usedEvents.size) {
|
||||
return false;
|
||||
}
|
||||
return listener;
|
||||
if (bindToRenderer) {
|
||||
subscribeToRenderEvents(usedEvents, compileElement);
|
||||
}
|
||||
subscribeToDirectiveEvents(usedEvents, directives, compileElement);
|
||||
generateHandleEventMethod(boundEvents, directives, compileElement);
|
||||
return true;
|
||||
}
|
||||
|
||||
get methodName() { return this._methodName; }
|
||||
get isAnimation() { return !!this.eventPhase; }
|
||||
function collectEvents(
|
||||
boundEvents: BoundEventAst[], directives: DirectiveAst[]): Map<string, EventSummary> {
|
||||
const usedEvents = new Map<string, EventSummary>();
|
||||
boundEvents.forEach((event) => { usedEvents.set(event.fullName, event); });
|
||||
directives.forEach((dirAst) => {
|
||||
dirAst.hostEvents.forEach((event) => { usedEvents.set(event.fullName, event); });
|
||||
});
|
||||
return usedEvents;
|
||||
}
|
||||
|
||||
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));
|
||||
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));
|
||||
}
|
||||
|
||||
addAction(
|
||||
hostEvent: BoundEventAst, directive: CompileDirectiveMetadata,
|
||||
directiveInstance: o.Expression) {
|
||||
if (isPresent(directive) && directive.isComponent) {
|
||||
this._hasComponentHostListener = true;
|
||||
});
|
||||
if (eventAndTargetExprs.length) {
|
||||
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]));
|
||||
}
|
||||
this._method.resetDebugInfo(this.compileElement.nodeIndex, hostEvent);
|
||||
var context = directiveInstance || this.compileElement.view.componentContext;
|
||||
const view = this.compileElement.view;
|
||||
}
|
||||
|
||||
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(
|
||||
view, directive ? null : view, context, hostEvent.handler,
|
||||
`${this.compileElement.nodeIndex}_${this._actionResultExprs.length}`);
|
||||
compileElement.view, compileElement.view, compileElement.view.componentContext,
|
||||
renderEvent.handler, `sub_${renderEventIdx}`);
|
||||
const trueStmts = evalResult.stmts;
|
||||
if (evalResult.preventDefault) {
|
||||
this._actionResultExprs.push(evalResult.preventDefault);
|
||||
}
|
||||
this._method.addStmts(evalResult.stmts);
|
||||
trueStmts.push(resultVar.set(evalResult.preventDefault.and(resultVar)).toStmt());
|
||||
}
|
||||
// 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]));
|
||||
}
|
||||
handleEventStmts.push(new o.ReturnStatement(resultVar));
|
||||
compileElement.view.methods.push(new o.ClassMethod(
|
||||
getHandleEventMethodName(compileElement.nodeIndex),
|
||||
[
|
||||
new o.FnParam(eventNameVar.name, o.STRING_TYPE),
|
||||
new o.FnParam(EventHandlerVars.event.name, o.DYNAMIC_TYPE)
|
||||
],
|
||||
handleEventStmts.finish(), o.BOOL_TYPE));
|
||||
}
|
||||
|
||||
listenToRenderer() {
|
||||
var listenExpr: o.Expression;
|
||||
var eventListener = o.THIS_EXPR.callMethod(
|
||||
function handleEventClosure(compileElement: CompileElement) {
|
||||
const handleEventMethodName = getHandleEventMethodName(compileElement.nodeIndex);
|
||||
return 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]));
|
||||
}
|
||||
[o.THIS_EXPR.prop(handleEventMethodName).callMethod(o.BuiltinMethod.Bind, [o.THIS_EXPR])]);
|
||||
}
|
||||
|
||||
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) => {
|
||||
var directiveInstance =
|
||||
compileElement.instances.get(identifierToken(directiveAst.directive.type).reference);
|
||||
directiveAst.hostEvents.forEach((hostEvent) => {
|
||||
var listener = CompileEventListener.getOrCreate(
|
||||
compileElement, hostEvent.target, hostEvent.name, hostEvent.phase, eventListeners);
|
||||
listener.addAction(hostEvent, directiveAst.directive, directiveInstance);
|
||||
});
|
||||
});
|
||||
|
||||
eventListeners.forEach((listener) => listener.finishMethod());
|
||||
return eventListeners;
|
||||
}
|
||||
|
||||
export function bindDirectiveOutputs(
|
||||
directiveAst: DirectiveAst, directiveInstance: o.Expression,
|
||||
eventListeners: CompileEventListener[]) {
|
||||
Object.keys(directiveAst.directive.outputs).forEach(observablePropName => {
|
||||
const eventName = directiveAst.directive.outputs[observablePropName];
|
||||
|
||||
eventListeners.filter(listener => listener.eventName == eventName).forEach((listener) => {
|
||||
listener.listenToDirective(directiveInstance, observablePropName);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function bindRenderOutputs(eventListeners: CompileEventListener[]) {
|
||||
eventListeners.forEach(listener => {
|
||||
// the animation listeners are handled within property_binder.ts to
|
||||
// allow them to be placed next to the animation factory statements
|
||||
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, '_');
|
||||
type EventSummary = {
|
||||
name: string,
|
||||
target: string,
|
||||
phase: string
|
||||
}
|
|
@ -7,16 +7,15 @@
|
|||
*/
|
||||
|
||||
import {CompileDirectiveMetadata, CompilePipeMetadata} from '../compile_metadata';
|
||||
import {DirectiveWrapperExpressions} from '../directive_wrapper_compiler';
|
||||
import * as o from '../output/output_ast';
|
||||
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 {CompileView} from './compile_view';
|
||||
import {DetectChangesVars} from './constants';
|
||||
|
||||
|
||||
|
||||
var STATE_IS_NEVER_CHECKED = o.THIS_EXPR.prop('numberOfChecks').identical(new o.LiteralExpr(0));
|
||||
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(
|
||||
provider: ProviderAst, providerInstance: o.Expression, compileElement: CompileElement) {
|
||||
var onDestroyMethod = compileElement.view.destroyMethod;
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,21 +11,22 @@ import {SecurityContext} from '@angular/core';
|
|||
import {createCheckBindingField, createCheckBindingStmt} from '../compiler_util/binding_util';
|
||||
import {ConvertPropertyBindingResult, convertPropertyBinding} from '../compiler_util/expression_converter';
|
||||
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 {isPresent} from '../facade/lang';
|
||||
import {Identifiers, resolveIdentifier} from '../identifiers';
|
||||
import * as o from '../output/output_ast';
|
||||
import {EMPTY_STATE as EMPTY_ANIMATION_STATE, LifecycleHooks, isDefaultChangeDetectionStrategy} from '../private_import_core';
|
||||
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 {CompileElement, CompileNode} from './compile_element';
|
||||
import {CompileMethod} from './compile_method';
|
||||
import {CompileView} from './compile_view';
|
||||
import {DetectChangesVars, ViewProperties} from './constants';
|
||||
import {CompileEventListener} from './event_binder';
|
||||
import {getHandleEventMethodName} from './util';
|
||||
|
||||
export function bindRenderText(
|
||||
boundText: BoundTextAst, compileNode: CompileNode, view: CompileView): void {
|
||||
|
@ -44,102 +45,51 @@ export function bindRenderText(
|
|||
.toStmt()]));
|
||||
}
|
||||
|
||||
function bindAndWriteToRenderer(
|
||||
boundProps: BoundElementPropertyAst[], context: o.Expression, compileElement: CompileElement,
|
||||
isHostProp: boolean, eventListeners: CompileEventListener[]) {
|
||||
export function bindRenderInputs(
|
||||
boundProps: BoundElementPropertyAst[], hasEvents: boolean, compileElement: CompileElement) {
|
||||
var view = compileElement.view;
|
||||
var renderNode = compileElement.renderNode;
|
||||
boundProps.forEach((boundProp) => {
|
||||
const bindingField = createCheckBindingField(view);
|
||||
view.detectChangesRenderPropertiesMethod.resetDebugInfo(compileElement.nodeIndex, boundProp);
|
||||
const evalResult = convertPropertyBinding(
|
||||
view, isHostProp ? null : view, context, boundProp.value, bindingField.bindingId);
|
||||
var updateStmts: o.Statement[] = [];
|
||||
view, view, compileElement.view.componentContext, boundProp.value, bindingField.bindingId);
|
||||
var checkBindingStmts: o.Statement[] = [];
|
||||
var compileMethod = view.detectChangesRenderPropertiesMethod;
|
||||
switch (boundProp.type) {
|
||||
case PropertyBindingType.Property:
|
||||
case PropertyBindingType.Attribute:
|
||||
case PropertyBindingType.Class:
|
||||
case PropertyBindingType.Style:
|
||||
updateStmts.push(...writeToRenderer(
|
||||
checkBindingStmts.push(...writeToRenderer(
|
||||
o.THIS_EXPR, boundProp, renderNode, evalResult.currValExpr,
|
||||
view.genConfig.logBindingUpdate));
|
||||
break;
|
||||
case PropertyBindingType.Animation:
|
||||
compileMethod = view.animationBindingsMethod;
|
||||
const detachStmts: o.Statement[] = [];
|
||||
|
||||
const animationName = boundProp.name;
|
||||
const targetViewExpr: o.Expression =
|
||||
isHostProp ? compileElement.appElement.prop('componentView') : o.THIS_EXPR;
|
||||
|
||||
const animationFnExpr =
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
const {updateStmts, detachStmts} = triggerAnimation(
|
||||
o.THIS_EXPR, o.THIS_EXPR, boundProp,
|
||||
(hasEvents ? o.THIS_EXPR.prop(getHandleEventMethodName(compileElement.nodeIndex)) :
|
||||
o.importExpr(resolveIdentifier(Identifiers.noop)))
|
||||
.callMethod(o.BuiltinMethod.Bind, [o.THIS_EXPR]),
|
||||
compileElement.renderNode, evalResult.currValExpr, bindingField.expression);
|
||||
checkBindingStmts.push(...updateStmts);
|
||||
view.detachMethod.addStmts(detachStmts);
|
||||
|
||||
break;
|
||||
}
|
||||
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(
|
||||
directiveAst: DirectiveAst, directiveWrapperInstance: o.Expression,
|
||||
compileElement: CompileElement, eventListeners: CompileEventListener[], elementName: string,
|
||||
compileElement: CompileElement, elementName: string,
|
||||
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.
|
||||
const runtimeSecurityCtxExprs =
|
||||
directiveAst.hostProperties.filter(boundProp => boundProp.needsRuntimeSecurityContext)
|
||||
.forEach((boundProp) => {
|
||||
.map((boundProp) => {
|
||||
let ctx: SecurityContext;
|
||||
switch (boundProp.type) {
|
||||
case PropertyBindingType.Property:
|
||||
|
@ -152,10 +102,13 @@ export function bindDirectiveHostProps(
|
|||
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(
|
||||
|
@ -187,9 +140,9 @@ export function bindDirectiveInputs(
|
|||
});
|
||||
var isOnPushComp = directiveAst.directive.isComponent &&
|
||||
!isDefaultChangeDetectionStrategy(directiveAst.directive.changeDetection);
|
||||
let directiveDetectChangesExpr = directiveWrapperInstance.callMethod(
|
||||
'detectChangesInInputProps',
|
||||
[o.THIS_EXPR, compileElement.renderNode, DetectChangesVars.throwOnChange]);
|
||||
let directiveDetectChangesExpr = DirectiveWrapperExpressions.ngDoCheck(
|
||||
directiveWrapperInstance, o.THIS_EXPR, compileElement.renderNode,
|
||||
DetectChangesVars.throwOnChange);
|
||||
const directiveDetectChangesStmt = isOnPushComp ?
|
||||
new o.IfStmt(directiveDetectChangesExpr, [compileElement.appElement.prop('componentView')
|
||||
.callMethod('markAsCheckOnce', [])
|
||||
|
|
|
@ -91,3 +91,7 @@ export function createFlatArray(expressions: o.Expression[]): o.Expression {
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function getHandleEventMethodName(elementIndex: number): string {
|
||||
return `handleEvent_${elementIndex}`;
|
||||
}
|
|
@ -11,8 +11,8 @@ import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventA
|
|||
|
||||
import {CompileElement} from './compile_element';
|
||||
import {CompileView} from './compile_view';
|
||||
import {CompileEventListener, bindDirectiveOutputs, bindRenderOutputs, collectEventListeners} from './event_binder';
|
||||
import {bindDirectiveAfterContentLifecycleCallbacks, bindDirectiveAfterViewLifecycleCallbacks, bindInjectableDestroyLifecycleCallbacks, bindPipeDestroyLifecycleCallbacks} from './lifecycle_binder';
|
||||
import {bindOutputs} from './event_binder';
|
||||
import {bindDirectiveAfterContentLifecycleCallbacks, bindDirectiveAfterViewLifecycleCallbacks, bindDirectiveWrapperLifecycleCallbacks, bindInjectableDestroyLifecycleCallbacks, bindPipeDestroyLifecycleCallbacks} from './lifecycle_binder';
|
||||
import {bindDirectiveHostProps, bindDirectiveInputs, bindRenderInputs, bindRenderText} from './property_binder';
|
||||
|
||||
export function bindView(
|
||||
|
@ -42,32 +42,29 @@ class ViewBinderVisitor implements TemplateAstVisitor {
|
|||
|
||||
visitElement(ast: ElementAst, parent: CompileElement): any {
|
||||
var compileElement = <CompileElement>this.view.nodes[this._nodeIndex++];
|
||||
var eventListeners: CompileEventListener[] = [];
|
||||
collectEventListeners(ast.outputs, ast.directives, compileElement).forEach(entry => {
|
||||
eventListeners.push(entry);
|
||||
});
|
||||
bindRenderInputs(ast.inputs, compileElement, eventListeners);
|
||||
bindRenderOutputs(eventListeners);
|
||||
const hasEvents = bindOutputs(ast.outputs, ast.directives, compileElement, true);
|
||||
bindRenderInputs(ast.inputs, hasEvents, compileElement);
|
||||
ast.directives.forEach((directiveAst, dirIndex) => {
|
||||
var directiveInstance = compileElement.instances.get(directiveAst.directive.type.reference);
|
||||
var directiveWrapperInstance =
|
||||
compileElement.directiveWrapperInstance.get(directiveAst.directive.type.reference);
|
||||
bindDirectiveInputs(directiveAst, directiveWrapperInstance, dirIndex, compileElement);
|
||||
|
||||
bindDirectiveHostProps(
|
||||
directiveAst, directiveWrapperInstance, compileElement, eventListeners, ast.name,
|
||||
this._schemaRegistry);
|
||||
bindDirectiveOutputs(directiveAst, directiveInstance, eventListeners);
|
||||
directiveAst, directiveWrapperInstance, compileElement, ast.name, this._schemaRegistry);
|
||||
});
|
||||
templateVisitAll(this, ast.children, compileElement);
|
||||
// afterContent and afterView lifecycles need to be called bottom up
|
||||
// so that children are notified before parents
|
||||
ast.directives.forEach((directiveAst) => {
|
||||
var directiveInstance = compileElement.instances.get(directiveAst.directive.type.reference);
|
||||
var directiveWrapperInstance =
|
||||
compileElement.directiveWrapperInstance.get(directiveAst.directive.type.reference);
|
||||
bindDirectiveAfterContentLifecycleCallbacks(
|
||||
directiveAst.directive, directiveInstance, compileElement);
|
||||
bindDirectiveAfterViewLifecycleCallbacks(
|
||||
directiveAst.directive, directiveInstance, compileElement);
|
||||
bindDirectiveWrapperLifecycleCallbacks(
|
||||
directiveAst, directiveWrapperInstance, compileElement);
|
||||
});
|
||||
ast.providers.forEach((providerAst) => {
|
||||
var providerInstance = compileElement.instances.get(providerAst.token.reference);
|
||||
|
@ -78,18 +75,19 @@ class ViewBinderVisitor implements TemplateAstVisitor {
|
|||
|
||||
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, parent: CompileElement): any {
|
||||
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) => {
|
||||
var directiveInstance = compileElement.instances.get(directiveAst.directive.type.reference);
|
||||
var directiveWrapperInstance =
|
||||
compileElement.directiveWrapperInstance.get(directiveAst.directive.type.reference);
|
||||
bindDirectiveInputs(directiveAst, directiveWrapperInstance, dirIndex, compileElement);
|
||||
|
||||
bindDirectiveOutputs(directiveAst, directiveInstance, eventListeners);
|
||||
bindDirectiveAfterContentLifecycleCallbacks(
|
||||
directiveAst.directive, directiveInstance, compileElement);
|
||||
bindDirectiveAfterViewLifecycleCallbacks(
|
||||
directiveAst.directive, directiveInstance, compileElement);
|
||||
bindDirectiveWrapperLifecycleCallbacks(
|
||||
directiveAst, directiveWrapperInstance, compileElement);
|
||||
});
|
||||
ast.providers.forEach((providerAst) => {
|
||||
var providerInstance = compileElement.instances.get(providerAst.token.reference);
|
||||
|
|
|
@ -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); }
|
||||
}
|
||||
|
@ -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);
|
||||
return (event?: any) => {
|
||||
return (eventName: string, event?: any) => {
|
||||
this._resetDebug();
|
||||
try {
|
||||
return superHandler(event);
|
||||
return superHandler(eventName, event);
|
||||
} catch (e) {
|
||||
this._rethrowWithContext(e);
|
||||
throw e;
|
||||
|
|
|
@ -400,14 +400,59 @@ export function selectOrCreateRenderHostElement(
|
|||
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> {
|
||||
length: number;
|
||||
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> {
|
||||
length = 0;
|
||||
get(index: number): any { return undefined; }
|
||||
set(index: number, value: any): void {}
|
||||
}
|
||||
|
||||
export class InlineArray2<T> implements InlineArray<T> {
|
||||
|
@ -422,6 +467,16 @@ export class InlineArray2<T> implements InlineArray<T> {
|
|||
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> {
|
||||
|
@ -441,6 +496,22 @@ export class InlineArray4<T> implements InlineArray<T> {
|
|||
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> {
|
||||
|
@ -469,6 +540,34 @@ export class InlineArray8<T> implements InlineArray<T> {
|
|||
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> {
|
||||
|
@ -515,6 +614,58 @@ export class InlineArray16<T> implements InlineArray<T> {
|
|||
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> {
|
||||
|
@ -524,6 +675,7 @@ export class InlineArrayDynamic<T> implements InlineArray<T> {
|
|||
constructor(public length: number, ...values: any[]) { this._values = values; }
|
||||
|
||||
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();
|
||||
|
|
Loading…
Reference in New Issue