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 {
|
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 {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};
|
||||||
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
@ -275,3 +369,84 @@ function reportParseErrors(parseErrors: ParseError[], console: Console) {
|
||||||
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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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}`);
|
||||||
|
|
|
@ -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; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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, '_');
|
|
||||||
}
|
}
|
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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', [])
|
||||||
|
|
|
@ -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}`;
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Loading…
Reference in New Issue