diff --git a/packages/compiler-cli/test/compliance/r3_view_compiler_template_spec.ts b/packages/compiler-cli/test/compliance/r3_view_compiler_template_spec.ts
index 89d54b6cc5..7345a25952 100644
--- a/packages/compiler-cli/test/compliance/r3_view_compiler_template_spec.ts
+++ b/packages/compiler-cli/test/compliance/r3_view_compiler_template_spec.ts
@@ -178,6 +178,59 @@ describe('compiler compliance: template', () => {
expectEmit(result.source, template, 'Incorrect template');
});
+ it('should correctly bind to implicit receiver in template', () => {
+ const files = {
+ app: {
+ 'spec.ts': `
+ import {Component, NgModule} from '@angular/core';
+
+ @Component({
+ selector: 'my-component',
+ template: \`
+
+
+ \`
+ })
+ export class MyComponent {
+ greet(val: any) {}
+ }
+
+ @NgModule({declarations: [MyComponent]})
+ export class MyModule {}
+ `
+ }
+ };
+
+ const template = `
+ function MyComponent_div_0_Template(rf, ctx) {
+ if (rf & 1) {
+ const $_r2$ = i0.ɵɵgetCurrentView();
+ $r3$.ɵɵelementStart(0, "div", $_c1$);
+ $r3$.ɵɵlistener("click", function MyComponent_div_0_Template_div_click_0_listener($event) {
+ i0.ɵɵrestoreView($_r2$);
+ const $ctx_r1$ = i0.ɵɵnextContext();
+ return $ctx_r1$.greet($ctx_r1$);
+ });
+ $r3$.ɵɵelementEnd();
+ }
+ }
+ // ...
+ function MyComponent_div_1_Template(rf, ctx) {
+ if (rf & 1) {
+ $r3$.ɵɵelement(0, "div", $_c3$);
+ } if (rf & 2) {
+ const $ctx_0$ = i0.ɵɵnextContext();
+ $r3$.ɵɵselect(0);
+ $r3$.ɵɵproperty("id", $ctx_0$);
+ }
+ }
+ `;
+
+ const result = compile(files, angularFiles);
+
+ expectEmit(result.source, template, 'Incorrect template');
+ });
+
it('should support ngFor context variables', () => {
const files = {
app: {
diff --git a/packages/compiler/src/compiler_util/expression_converter.ts b/packages/compiler/src/compiler_util/expression_converter.ts
index 67cc3ceeb8..971aec3223 100644
--- a/packages/compiler/src/compiler_util/expression_converter.ts
+++ b/packages/compiler/src/compiler_util/expression_converter.ts
@@ -13,7 +13,10 @@ import {ParseSourceSpan} from '../parse_util';
export class EventHandlerVars { static event = o.variable('$event'); }
-export interface LocalResolver { getLocal(name: string): o.Expression|null; }
+export interface LocalResolver {
+ getLocal(name: string): o.Expression|null;
+ notifyImplicitReceiverUse(): void;
+}
export class ConvertActionBindingResult {
/**
@@ -99,6 +102,11 @@ export function convertActionBinding(
const actionStmts: o.Statement[] = [];
flattenStatements(actionWithoutBuiltins.visit(visitor, _Mode.Statement), actionStmts);
prependTemporaryDecls(visitor.temporaryCount, bindingId, actionStmts);
+
+ if (visitor.usesImplicitReceiver) {
+ localResolver.notifyImplicitReceiverUse();
+ }
+
const lastIndex = actionStmts.length - 1;
let preventDefaultVar: o.ReadVarExpr = null !;
if (lastIndex >= 0) {
@@ -160,6 +168,10 @@ export function convertPropertyBinding(
const outputExpr: o.Expression = expressionWithoutBuiltins.visit(visitor, _Mode.Expression);
const stmts: o.Statement[] = getStatementsFromVisitor(visitor, bindingId);
+ if (visitor.usesImplicitReceiver) {
+ localResolver.notifyImplicitReceiverUse();
+ }
+
if (visitor.temporaryCount === 0 && form == BindingForm.TrySimple) {
return new ConvertPropertyBindingResult([], outputExpr);
}
@@ -192,6 +204,10 @@ export function convertUpdateArguments(
const outputExpr: o.InvokeFunctionExpr =
expressionWithArgumentsToExtract.visit(visitor, _Mode.Expression);
+ if (visitor.usesImplicitReceiver) {
+ localResolver.notifyImplicitReceiverUse();
+ }
+
const stmts = getStatementsFromVisitor(visitor, bindingId);
// Removing the first argument, because it was a length for ViewEngine, not Ivy.
@@ -290,6 +306,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
private _resultMap = new Map();
private _currentTemporary: number = 0;
public temporaryCount: number = 0;
+ public usesImplicitReceiver: boolean = false;
constructor(
private _localResolver: LocalResolver, private _implicitReceiver: o.Expression,
@@ -387,6 +404,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
visitImplicitReceiver(ast: cdAst.ImplicitReceiver, mode: _Mode): any {
ensureExpressionMode(mode, ast);
+ this.usesImplicitReceiver = true;
return this._implicitReceiver;
}
@@ -462,11 +480,15 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
return this.convertSafeAccess(ast, leftMostSafe, mode);
} else {
const args = this.visitAll(ast.args, _Mode.Expression);
+ const prevUsesImplicitReceiver = this.usesImplicitReceiver;
let result: any = null;
const receiver = this._visit(ast.receiver, _Mode.Expression);
if (receiver === this._implicitReceiver) {
const varExpr = this._getLocal(ast.name);
if (varExpr) {
+ // Restore the previous "usesImplicitReceiver" state since the implicit
+ // receiver has been replaced with a resolved local expression.
+ this.usesImplicitReceiver = prevUsesImplicitReceiver;
result = varExpr.callFn(args);
}
}
@@ -492,9 +514,15 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
return this.convertSafeAccess(ast, leftMostSafe, mode);
} else {
let result: any = null;
+ const prevUsesImplicitReceiver = this.usesImplicitReceiver;
const receiver = this._visit(ast.receiver, _Mode.Expression);
if (receiver === this._implicitReceiver) {
result = this._getLocal(ast.name);
+ if (result) {
+ // Restore the previous "usesImplicitReceiver" state since the implicit
+ // receiver has been replaced with a resolved local expression.
+ this.usesImplicitReceiver = prevUsesImplicitReceiver;
+ }
}
if (result == null) {
result = receiver.prop(ast.name);
@@ -505,6 +533,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
visitPropertyWrite(ast: cdAst.PropertyWrite, mode: _Mode): any {
const receiver: o.Expression = this._visit(ast.receiver, _Mode.Expression);
+ const prevUsesImplicitReceiver = this.usesImplicitReceiver;
let varExpr: o.ReadPropExpr|null = null;
if (receiver === this._implicitReceiver) {
@@ -515,6 +544,9 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
// to a 'context.property' value and will be used as the target of the
// write expression.
varExpr = localExpr;
+ // Restore the previous "usesImplicitReceiver" state since the implicit
+ // receiver has been replaced with a resolved local expression.
+ this.usesImplicitReceiver = prevUsesImplicitReceiver;
} else {
// Otherwise it's an error.
throw new Error('Cannot assign to a reference or variable!');
@@ -753,6 +785,7 @@ function flattenStatements(arg: any, output: o.Statement[]) {
}
class DefaultLocalResolver implements LocalResolver {
+ notifyImplicitReceiverUse(): void {}
getLocal(name: string): o.Expression|null {
if (name === EventHandlerVars.event.name) {
return EventHandlerVars.event;
diff --git a/packages/compiler/src/render3/view/compiler.ts b/packages/compiler/src/render3/view/compiler.ts
index 5d6378730d..9ec1f5a2b4 100644
--- a/packages/compiler/src/render3/view/compiler.ts
+++ b/packages/compiler/src/render3/view/compiler.ts
@@ -632,7 +632,7 @@ function createHostBindingsFunction(
const eventBindings =
bindingParser.createDirectiveHostEventAsts(directiveSummary, hostBindingSourceSpan);
if (eventBindings && eventBindings.length) {
- const listeners = createHostListeners(bindingContext, eventBindings, name);
+ const listeners = createHostListeners(eventBindings, name);
createStatements.push(...listeners);
}
@@ -781,16 +781,14 @@ function getBindingNameAndInstruction(binding: ParsedProperty):
return {bindingName, instruction, isAttribute: !!attrMatches};
}
-function createHostListeners(
- bindingContext: o.Expression, eventBindings: ParsedEvent[], name?: string): o.Statement[] {
+function createHostListeners(eventBindings: ParsedEvent[], name?: string): o.Statement[] {
return eventBindings.map(binding => {
let bindingName = binding.name && sanitizeIdentifier(binding.name);
const bindingFnName = binding.type === ParsedEventType.Animation ?
prepareSyntheticListenerFunctionName(bindingName, binding.targetOrPhase) :
bindingName;
const handlerName = name && bindingName ? `${name}_${bindingFnName}_HostBindingHandler` : null;
- const params = prepareEventListenerParameters(
- BoundEvent.fromParsedEvent(binding), bindingContext, handlerName);
+ const params = prepareEventListenerParameters(BoundEvent.fromParsedEvent(binding), handlerName);
const instruction =
binding.type == ParsedEventType.Animation ? R3.componentHostSyntheticListener : R3.listener;
return o.importExpr(instruction).callFn(params).toStmt();
diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts
index 61477223e5..7c219556d1 100644
--- a/packages/compiler/src/render3/view/template.ts
+++ b/packages/compiler/src/render3/view/template.ts
@@ -59,7 +59,7 @@ export function renderFlagCheckIfStmt(
}
export function prepareEventListenerParameters(
- eventAst: t.BoundEvent, bindingContext: o.Expression, handlerName: string | null = null,
+ eventAst: t.BoundEvent, handlerName: string | null = null,
scope: BindingScope | null = null): o.Expression[] {
const {type, name, target, phase, handler} = eventAst;
if (target && !GLOBAL_TARGET_RESOLVERS.has(target)) {
@@ -67,8 +67,11 @@ export function prepareEventListenerParameters(
Supported list of global targets: ${Array.from(GLOBAL_TARGET_RESOLVERS.keys())}.`);
}
+ const implicitReceiverExpr = (scope === null || scope.bindingLevel === 0) ?
+ o.variable(CONTEXT_NAME) :
+ scope.getOrCreateSharedContextVar(0);
const bindingExpr = convertActionBinding(
- scope, bindingContext, handler, 'b', () => error('Unexpected interpolation'),
+ scope, implicitReceiverExpr, handler, 'b', () => error('Unexpected interpolation'),
eventAst.handlerSpan);
const statements = [];
@@ -152,6 +155,10 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver
// track it to properly adjust projection slot index in the `projection` instruction.
private _ngContentSelectorsOffset = 0;
+ // Expression that should be used as implicit receiver when converting template
+ // expressions to output AST.
+ private _implicitReceiverExpr: o.ReadVarExpr|null = null;
+
constructor(
private constantPool: ConstantPool, parentBindingScope: BindingScope, private level = 0,
private contextName: string|null, private i18nContext: I18nContext|null,
@@ -306,6 +313,9 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver
// LocalResolver
getLocal(name: string): o.Expression|null { return this._bindingScope.get(name); }
+ // LocalResolver
+ notifyImplicitReceiverUse(): void { this._bindingScope.notifyImplicitReceiverUse(); }
+
i18nTranslate(
message: i18n.Message, params: {[name: string]: o.Expression} = {}, ref?: o.ReadVarExpr,
transformFn?: (raw: o.ReadVarExpr) => o.Expression): o.ReadVarExpr {
@@ -448,8 +458,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver
if (bindings.size) {
bindings.forEach(binding => {
this.updateInstruction(
- index, span, R3.i18nExp,
- () => [this.convertPropertyBinding(o.variable(CONTEXT_NAME), binding)]);
+ index, span, R3.i18nExp, () => [this.convertPropertyBinding(binding)]);
});
this.updateInstruction(index, span, R3.i18nApply, [o.literal(index)]);
}
@@ -598,8 +607,6 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver
this.addNamespaceInstruction(currentNamespace, element);
}
- const implicit = o.variable(CONTEXT_NAME);
-
if (this.i18n) {
this.i18n.appendElement(element.i18n !, elementIndex);
}
@@ -649,7 +656,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver
hasBindings = true;
this.updateInstruction(
elementIndex, element.sourceSpan, R3.i18nExp,
- () => [this.convertExpressionBinding(implicit, expression)]);
+ () => [this.convertExpressionBinding(expression)]);
});
}
}
@@ -671,7 +678,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver
// (things like `styleProp`, `classProp`, etc..) are applied later on in this
// file
this.processStylingInstruction(
- elementIndex, implicit,
+ elementIndex,
stylingBuilder.buildStylingInstruction(element.sourceSpan, this.constantPool), true);
// Generate Listeners (outputs)
@@ -697,7 +704,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver
for (let i = 0; i <= limit; i++) {
const instruction = stylingInstructions[i];
this._bindingSlots += instruction.allocateBindingSlots;
- this.processStylingInstruction(elementIndex, implicit, instruction, false);
+ this.processStylingInstruction(elementIndex, instruction, false);
}
// the reason why `undefined` is used is because the renderer understands this as a
@@ -726,7 +733,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver
this.updateInstruction(elementIndex, input.sourceSpan, R3.property, () => {
return [
o.literal(bindingName),
- (hasValue ? this.convertPropertyBinding(implicit, value, /* skipBindFn */ true) :
+ (hasValue ? this.convertPropertyBinding(value, /* skipBindFn */ true) :
emptyValueBindInstruction),
];
});
@@ -764,7 +771,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver
} else {
// [prop]="value"
this.boundUpdateInstruction(
- R3.property, elementIndex, attrName, input, implicit, value, params);
+ R3.property, elementIndex, attrName, input, value, params);
}
} else if (inputType === BindingType.Attribute) {
if (value instanceof Interpolation && getInterpolationArgsLength(value) > 1) {
@@ -776,14 +783,14 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver
const boundValue = value instanceof Interpolation ? value.expressions[0] : value;
// [attr.name]="value" or attr.name="{{value}}"
this.boundUpdateInstruction(
- R3.attribute, elementIndex, attrName, input, implicit, boundValue, params);
+ R3.attribute, elementIndex, attrName, input, boundValue, params);
}
} else {
// class prop
this.updateInstruction(elementIndex, input.sourceSpan, R3.classProp, () => {
return [
- o.literal(elementIndex), o.literal(attrName),
- this.convertPropertyBinding(implicit, value), ...params
+ o.literal(elementIndex), o.literal(attrName), this.convertPropertyBinding(value),
+ ...params
];
});
}
@@ -817,9 +824,9 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver
*/
boundUpdateInstruction(
instruction: o.ExternalReference, elementIndex: number, attrName: string,
- input: t.BoundAttribute, implicit: o.ReadVarExpr, value: any, params: any[]) {
+ input: t.BoundAttribute, value: any, params: any[]) {
this.updateInstruction(elementIndex, input.sourceSpan, instruction, () => {
- return [o.literal(attrName), this.convertPropertyBinding(implicit, value, true), ...params];
+ return [o.literal(attrName), this.convertPropertyBinding(value, true), ...params];
});
}
@@ -832,9 +839,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver
input: t.BoundAttribute, value: any, params: any[]) {
this.updateInstruction(
elementIndex, input.sourceSpan, instruction,
- () =>
- [o.literal(attrName),
- ...this.getUpdateInstructionArguments(o.variable(CONTEXT_NAME), value), ...params]);
+ () => [o.literal(attrName), ...this.getUpdateInstructionArguments(value), ...params]);
}
visitTemplate(template: t.Template) {
@@ -904,13 +909,12 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver
});
// handle property bindings e.g. ɵɵproperty('ngForOf', ctx.items), et al;
- const context = o.variable(CONTEXT_NAME);
- this.templatePropertyBindings(template, templateIndex, context, template.templateAttrs);
+ this.templatePropertyBindings(template, templateIndex, template.templateAttrs);
// Only add normal input/output binding instructions on explicit ng-template elements.
if (template.tagName === NG_TEMPLATE_TAG_NAME) {
// Add the input bindings
- this.templatePropertyBindings(template, templateIndex, context, template.inputs);
+ this.templatePropertyBindings(template, templateIndex, template.inputs);
// Generate listeners for directive output
template.outputs.forEach((outputAst: t.BoundEvent) => {
this.creationInstruction(
@@ -948,12 +952,11 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver
if (value instanceof Interpolation) {
this.updateInstruction(
nodeIndex, text.sourceSpan, getTextInterpolationExpression(value),
- () => this.getUpdateInstructionArguments(o.variable(CONTEXT_NAME), value));
+ () => this.getUpdateInstructionArguments(value));
} else {
this.updateInstruction(
nodeIndex, text.sourceSpan, R3.textBinding,
- () =>
- [o.literal(nodeIndex), this.convertPropertyBinding(o.variable(CONTEXT_NAME), value)]);
+ () => [o.literal(nodeIndex), this.convertPropertyBinding(value)]);
}
}
@@ -1019,8 +1022,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver
private bindingContext() { return `${this._bindingContext++}`; }
private templatePropertyBindings(
- template: t.Template, templateIndex: number, context: o.ReadVarExpr,
- attrs: (t.BoundAttribute|t.TextAttribute)[]) {
+ template: t.Template, templateIndex: number, attrs: (t.BoundAttribute|t.TextAttribute)[]) {
attrs.forEach(input => {
if (input instanceof t.BoundAttribute) {
const value = input.value.visit(this._valueConverter);
@@ -1029,7 +1031,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver
this.allocateBindingSlots(value);
this.updateInstruction(
templateIndex, template.sourceSpan, R3.property,
- () => [o.literal(input.name), this.convertPropertyBinding(context, value, true)]);
+ () => [o.literal(input.name), this.convertPropertyBinding(value, true)]);
}
}
});
@@ -1049,10 +1051,10 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver
}
private processStylingInstruction(
- elementIndex: number, implicit: any, instruction: Instruction|null, createMode: boolean) {
+ elementIndex: number, instruction: Instruction|null, createMode: boolean) {
if (instruction) {
const paramsFn = () =>
- instruction.buildParams(value => this.convertPropertyBinding(implicit, value, true));
+ instruction.buildParams(value => this.convertPropertyBinding(value, true));
if (createMode) {
this.creationInstruction(instruction.sourceSpan, instruction.reference, paramsFn);
} else {
@@ -1090,23 +1092,38 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver
this._bindingSlots += value instanceof Interpolation ? value.expressions.length : 1;
}
- private convertExpressionBinding(implicit: o.Expression, value: AST): o.Expression {
- const convertedPropertyBinding =
- convertPropertyBinding(this, implicit, value, this.bindingContext(), BindingForm.TrySimple);
+ /**
+ * Gets an expression that refers to the implicit receiver. The implicit
+ * receiver is always the root level context.
+ */
+ private getImplicitReceiverExpr(): o.ReadVarExpr {
+ if (this._implicitReceiverExpr) {
+ return this._implicitReceiverExpr;
+ }
+
+ return this._implicitReceiverExpr = this.level === 0 ?
+ o.variable(CONTEXT_NAME) :
+ this._bindingScope.getOrCreateSharedContextVar(0);
+ }
+
+ private convertExpressionBinding(value: AST): o.Expression {
+ const convertedPropertyBinding = convertPropertyBinding(
+ this, this.getImplicitReceiverExpr(), value, this.bindingContext(), BindingForm.TrySimple);
const valExpr = convertedPropertyBinding.currValExpr;
+
return o.importExpr(R3.bind).callFn([valExpr]);
}
- private convertPropertyBinding(implicit: o.Expression, value: AST, skipBindFn?: boolean):
- o.Expression {
+ private convertPropertyBinding(value: AST, skipBindFn?: boolean): o.Expression {
const interpolationFn =
value instanceof Interpolation ? interpolate : () => error('Unexpected interpolation');
const convertedPropertyBinding = convertPropertyBinding(
- this, implicit, value, this.bindingContext(), BindingForm.TrySimple, interpolationFn);
+ this, this.getImplicitReceiverExpr(), value, this.bindingContext(), BindingForm.TrySimple,
+ interpolationFn);
+ const valExpr = convertedPropertyBinding.currValExpr;
this._tempVariables.push(...convertedPropertyBinding.stmts);
- const valExpr = convertedPropertyBinding.currValExpr;
return value instanceof Interpolation || skipBindFn ? valExpr :
o.importExpr(R3.bind).callFn([valExpr]);
}
@@ -1115,13 +1132,12 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver
* Gets a list of argument expressions to pass to an update instruction expression. Also updates
* the temp variables state with temp variables that were identified as needing to be created
* while visiting the arguments.
- * @param contextExpression The expression for the context variable used to create arguments
* @param value The original expression we will be resolving an arguments list from.
*/
- private getUpdateInstructionArguments(contextExpression: o.Expression, value: AST):
- o.Expression[] {
+ private getUpdateInstructionArguments(value: AST): o.Expression[] {
const {args, stmts} =
- convertUpdateArguments(this, contextExpression, value, this.bindingContext());
+ convertUpdateArguments(this, this.getImplicitReceiverExpr(), value, this.bindingContext());
+
this._tempVariables.push(...stmts);
return args;
}
@@ -1265,8 +1281,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver
sanitizeIdentifier(eventName);
const handlerName = `${this.templateName}_${tagName}_${bindingFnName}_${index}_listener`;
const scope = this._bindingScope.nestedScope(this._bindingScope.bindingLevel);
- const context = o.variable(CONTEXT_NAME);
- return prepareEventListenerParameters(outputAst, context, handlerName, scope);
+ return prepareEventListenerParameters(outputAst, handlerName, scope);
};
}
}
@@ -1544,14 +1559,38 @@ export class BindingScope implements LocalResolver {
return this;
}
+ // Implemented as part of LocalResolver.
getLocal(name: string): (o.Expression|null) { return this.get(name); }
+ // Implemented as part of LocalResolver.
+ notifyImplicitReceiverUse(): void {
+ if (this.bindingLevel !== 0) {
+ // Since the implicit receiver is accessed in an embedded view, we need to
+ // ensure that we declare a shared context variable for the current template
+ // in the update variables.
+ this.map.get(SHARED_CONTEXT_KEY + 0) !.declare = true;
+ }
+ }
+
nestedScope(level: number): BindingScope {
const newScope = new BindingScope(level, this);
if (level > 0) newScope.generateSharedContextVar(0);
return newScope;
}
+ /**
+ * Gets or creates a shared context variable and returns its expression. Note that
+ * this does not mean that the shared variable will be declared. Variables in the
+ * binding scope will be only declared if they are used.
+ */
+ getOrCreateSharedContextVar(retrievalLevel: number): o.ReadVarExpr {
+ const bindingKey = SHARED_CONTEXT_KEY + retrievalLevel;
+ if (!this.map.has(bindingKey)) {
+ this.generateSharedContextVar(retrievalLevel);
+ }
+ return this.map.get(bindingKey) !.lhs;
+ }
+
getSharedContextName(retrievalLevel: number): o.ReadVarExpr|null {
const sharedCtxObj = this.map.get(SHARED_CONTEXT_KEY + retrievalLevel);
return sharedCtxObj && sharedCtxObj.declare ? sharedCtxObj.lhs : null;
diff --git a/packages/compiler/src/view_compiler/type_check_compiler.ts b/packages/compiler/src/view_compiler/type_check_compiler.ts
index 8d5bfeb6a7..f617c47e51 100644
--- a/packages/compiler/src/view_compiler/type_check_compiler.ts
+++ b/packages/compiler/src/view_compiler/type_check_compiler.ts
@@ -79,6 +79,7 @@ interface Expression {
const DYNAMIC_VAR_NAME = '_any';
class TypeCheckLocalResolver implements LocalResolver {
+ notifyImplicitReceiverUse(): void {}
getLocal(name: string): o.Expression|null {
if (name === EventHandlerVars.event.name) {
// References to the event should not be type-checked.
@@ -284,6 +285,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver {
}
}
+ notifyImplicitReceiverUse(): void {}
getLocal(name: string): o.Expression|null {
if (name == EventHandlerVars.event.name) {
return o.variable(this.getOutputVar(o.BuiltinTypeName.Dynamic));
diff --git a/packages/compiler/src/view_compiler/view_compiler.ts b/packages/compiler/src/view_compiler/view_compiler.ts
index e97e3291f6..8310f7e104 100644
--- a/packages/compiler/src/view_compiler/view_compiler.ts
+++ b/packages/compiler/src/view_compiler/view_compiler.ts
@@ -687,6 +687,12 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver {
return null;
}
+ notifyImplicitReceiverUse(): void {
+ // Not needed in View Engine as View Engine walks through the generated
+ // expressions to figure out if the implicit receiver is used and needs
+ // to be generated as part of the pre-update statements.
+ }
+
private _createLiteralArrayConverter(sourceSpan: ParseSourceSpan, argCount: number):
BuiltinConverter {
if (argCount === 0) {
diff --git a/packages/core/test/acceptance/embedded_views_spec.ts b/packages/core/test/acceptance/embedded_views_spec.ts
new file mode 100644
index 0000000000..a4fd8ba31b
--- /dev/null
+++ b/packages/core/test/acceptance/embedded_views_spec.ts
@@ -0,0 +1,75 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+import {Component, Input} from '@angular/core';
+import {TestBed} from '@angular/core/testing';
+
+describe('embedded views', () => {
+
+ it('should correctly resolve the implicit receiver in expressions', () => {
+ const items: string[] = [];
+
+ @Component({
+ selector: 'child-cmp',
+ template: 'Child',
+ })
+ class ChildCmp {
+ @Input() addItemFn: Function|undefined;
+ }
+
+ @Component({
+ template: ``,
+ })
+ class TestCmp {
+ item: string = 'CmpItem';
+ addItem() { items.push(this.item); }
+ }
+
+ TestBed.configureTestingModule({declarations: [ChildCmp, TestCmp]});
+ const fixture = TestBed.createComponent(TestCmp);
+ fixture.detectChanges();
+
+ const childCmp: ChildCmp = fixture.debugElement.children[0].componentInstance;
+
+ childCmp.addItemFn !();
+ childCmp.addItemFn !();
+
+ expect(items).toEqual(['CmpItem', 'CmpItem']);
+ });
+
+ it('should resolve template input variables through the implicit receiver', () => {
+ @Component({template: `{{this.a}}`})
+ class TestCmp {
+ }
+
+ TestBed.configureTestingModule({declarations: [TestCmp]});
+ const fixture = TestBed.createComponent(TestCmp);
+ fixture.detectChanges();
+
+ expect(fixture.nativeElement.textContent).toBe('true');
+ });
+
+ it('should component instance variables through the implicit receiver', () => {
+ @Component({
+ template: `
+
+ {{this.myProp}}{{myProp}}
+ `
+ })
+ class TestCmp {
+ myProp = 'Hello';
+ }
+
+ TestBed.configureTestingModule({declarations: [TestCmp]});
+ const fixture = TestBed.createComponent(TestCmp);
+ fixture.detectChanges();
+
+ expect(fixture.nativeElement.textContent).toBe('HelloHello');
+ });
+
+});