diff --git a/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts b/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts index 3435abc781..ba02f37428 100644 --- a/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts @@ -590,6 +590,7 @@ describe('compiler compliance', () => { hostBindings: function HostBindingDir_HostBindings(dirIndex, elIndex) { $r3$.ɵelementProperty(elIndex, "id", $r3$.ɵbind($r3$.ɵloadDirective(dirIndex).dirId)); }, + hostVars: 1, features: [$r3$.ɵPublicFeature] }); `; @@ -600,6 +601,53 @@ describe('compiler compliance', () => { expectEmit(source, HostBindingDirDeclaration, 'Invalid host binding code'); }); + it('should support host bindings with pure functions', () => { + const files = { + app: { + 'spec.ts': ` + import {Component, NgModule} from '@angular/core'; + + @Component({ + selector: 'host-binding-comp', + host: { + '[id]': '["red", id]' + }, + template: '' + }) + export class HostBindingComp { + id = 'some id'; + } + + @NgModule({declarations: [HostBindingComp]}) + export class MyModule {} + ` + } + }; + + const HostBindingCompDeclaration = ` + const $ff$ = function ($v$) { return ["red", $v$]; }; + … + HostBindingComp.ngComponentDef = $r3$.ɵdefineComponent({ + type: HostBindingComp, + selectors: [["host-binding-comp"]], + factory: function HostBindingComp_Factory(t) { return new (t || HostBindingComp)(); }, + hostBindings: function HostBindingComp_HostBindings(dirIndex, elIndex) { + $r3$.ɵelementProperty(elIndex, "id", $r3$.ɵbind($r3$.ɵpureFunction1(1, $ff$, $r3$.ɵloadDirective(dirIndex).id))); + }, + hostVars: 3, + features: [$r3$.ɵPublicFeature], + consts: 0, + vars: 0, + template: function HostBindingComp_Template(rf, ctx) {} + }); + `; + + const result = compile(files, angularFiles); + const source = result.source; + + expectEmit(source, HostBindingCompDeclaration, 'Invalid host binding code'); + }); + it('should support structural directives', () => { const files = { app: { diff --git a/packages/compiler/src/render3/view/compiler.ts b/packages/compiler/src/render3/view/compiler.ts index 4bf6446258..ff1db8bf4a 100644 --- a/packages/compiler/src/render3/view/compiler.ts +++ b/packages/compiler/src/render3/view/compiler.ts @@ -26,7 +26,7 @@ import {Render3ParseResult} from '../r3_template_transform'; import {typeWithParameters} from '../util'; import {R3ComponentDef, R3ComponentMetadata, R3DirectiveDef, R3DirectiveMetadata, R3QueryMetadata} from './api'; -import {BindingScope, TemplateDefinitionBuilder, renderFlagCheckIfStmt} from './template'; +import {BindingScope, TemplateDefinitionBuilder, ValueConverter, renderFlagCheckIfStmt} from './template'; import {CONTEXT_NAME, DefinitionMap, RENDER_FLAGS, TEMPORARY_NAME, asLiteral, conditionallyCreateMapObjectLiteral, getQueryPredicate, temporaryAllocator} from './util'; const EMPTY_ARRAY: any[] = []; @@ -56,8 +56,22 @@ function baseDirectiveFields( definitionMap.set('contentQueriesRefresh', createContentQueriesRefreshFunction(meta)); + // Initialize hostVars to number of bound host properties (interpolations illegal) + let hostVars = Object.keys(meta.host.properties).length; + // e.g. `hostBindings: (dirIndex, elIndex) => { ... } - definitionMap.set('hostBindings', createHostBindingsFunction(meta, bindingParser)); + definitionMap.set( + 'hostBindings', + createHostBindingsFunction(meta, bindingParser, constantPool, (slots: number) => { + const originalSlots = hostVars; + hostVars += slots; + return originalSlots; + })); + + if (hostVars) { + // e.g. `hostVars: 2 + definitionMap.set('hostVars', o.literal(hostVars)); + } // e.g. `attributes: ['role', 'listbox']` definitionMap.set('attributes', createHostAttributesArray(meta)); @@ -521,7 +535,8 @@ function createViewQueriesFunction( // Return a host binding function or null if one is not necessary. function createHostBindingsFunction( - meta: R3DirectiveMetadata, bindingParser: BindingParser): o.Expression|null { + meta: R3DirectiveMetadata, bindingParser: BindingParser, constantPool: ConstantPool, + allocatePureFunctionSlots: (slots: number) => number): o.Expression|null { const statements: o.Statement[] = []; const hostBindingSourceSpan = meta.typeSourceSpan; @@ -532,9 +547,16 @@ function createHostBindingsFunction( const bindings = bindingParser.createBoundHostProperties(directiveSummary, hostBindingSourceSpan); const bindingContext = o.importExpr(R3.loadDirective).callFn([o.variable('dirIndex')]); if (bindings) { + const valueConverter = new ValueConverter( + constantPool, + /* new nodes are illegal here */ () => error('Unexpected node'), allocatePureFunctionSlots, + /* pipes are illegal here */ () => error('Unexpected pipe')); + for (const binding of bindings) { + // resolve literal arrays and literal objects + const value = binding.expression.visit(valueConverter); const bindingExpr = convertPropertyBinding( - null, bindingContext, binding.expression, 'b', BindingForm.TrySimple, + null, bindingContext, value, 'b', BindingForm.TrySimple, () => error('Unexpected interpolation')); statements.push(...bindingExpr.stmts); statements.push(o.importExpr(R3.elementProperty) diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index 09827c1cbc..94aeaf6241 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -908,7 +908,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver } } -class ValueConverter extends AstMemoryEfficientTransformer { +export class ValueConverter extends AstMemoryEfficientTransformer { private _pipeBindExprs: FunctionCall[] = []; constructor(