From dc300c5c4172e1f8a6f3f84da6797e427e3626c0 Mon Sep 17 00:00:00 2001 From: Andrew Kushnir Date: Tue, 20 Nov 2018 15:20:19 -0800 Subject: [PATCH] feat(ivy): render flags support in host bindings function (FW-649) (#27204) PR Close #27204 --- .../r3_view_compiler_binding_spec.ts | 18 ++-- .../r3_view_compiler_styling_spec.ts | 64 ++++++++----- .../compiler-cli/test/ngtsc/ngtsc_spec.ts | 86 +++++++++++------ .../compiler/src/injectable_compiler_2.ts | 1 - packages/compiler/src/render3/r3_factory.ts | 17 ---- .../src/render3/r3_module_compiler.ts | 1 - .../compiler/src/render3/r3_pipe_compiler.ts | 1 - .../compiler/src/render3/view/compiler.ts | 81 ++++++++-------- packages/compiler/src/render3/view/styling.ts | 27 +++--- packages/core/src/render3/definition.ts | 6 +- .../features/inherit_definition_feature.ts | 6 +- packages/core/src/render3/instructions.ts | 80 ++++++++-------- .../core/src/render3/interfaces/definition.ts | 4 +- packages/core/src/render3/interfaces/view.ts | 2 +- .../Inherit_definition_feature_spec.ts | 14 +-- packages/core/test/render3/di_spec.ts | 6 +- .../core/test/render3/host_binding_spec.ts | 94 +++++++++++-------- .../core/test/render3/integration_spec.ts | 16 ++-- packages/core/test/render3/ivy/jit_spec.ts | 2 +- packages/core/test/render3/listeners_spec.ts | 49 ++++++++-- .../test/render3/view_container_ref_spec.ts | 7 +- 21 files changed, 334 insertions(+), 248 deletions(-) diff --git a/packages/compiler-cli/test/compliance/r3_view_compiler_binding_spec.ts b/packages/compiler-cli/test/compliance/r3_view_compiler_binding_spec.ts index b658871864..f40fbbd7e1 100644 --- a/packages/compiler-cli/test/compliance/r3_view_compiler_binding_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_view_compiler_binding_spec.ts @@ -145,8 +145,10 @@ describe('compiler compliance: bindings', () => { type: HostBindingDir, selectors: [["", "hostBindingDir", ""]], factory: function HostBindingDir_Factory(t) { return new (t || HostBindingDir)(); }, - hostBindings: function HostBindingDir_HostBindings(dirIndex, elIndex) { - $r3$.ɵelementProperty(elIndex, "id", $r3$.ɵbind($r3$.ɵload(dirIndex).dirId)); + hostBindings: function HostBindingDir_HostBindings(rf, ctx, elIndex) { + if (rf & 2) { + $r3$.ɵelementProperty(elIndex, "id", $r3$.ɵbind(ctx.dirId)); + } }, hostVars: 1 }); @@ -188,8 +190,10 @@ describe('compiler compliance: bindings', () => { 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$.ɵload(dirIndex).id))); + hostBindings: function HostBindingComp_HostBindings(rf, ctx, elIndex) { + if (rf & 2) { + $r3$.ɵelementProperty(elIndex, "id", $r3$.ɵbind($r3$.ɵpureFunction1(1, $ff$, ctx.id))); + } }, hostVars: 3, consts: 0, @@ -232,8 +236,10 @@ describe('compiler compliance: bindings', () => { type: HostAttributeDir, selectors: [["", "hostAttributeDir", ""]], factory: function HostAttributeDir_Factory(t) { return new (t || HostAttributeDir)(); }, - hostBindings: function HostAttributeDir_HostBindings(dirIndex, elIndex) { - $r3$.ɵelementAttribute(elIndex, "required", $r3$.ɵbind($r3$.ɵload(dirIndex).required)); + hostBindings: function HostAttributeDir_HostBindings(rf, ctx, elIndex) { + if (rf & 2) { + $r3$.ɵelementAttribute(elIndex, "required", $r3$.ɵbind(ctx.required)); + } }, hostVars: 1 }); diff --git a/packages/compiler-cli/test/compliance/r3_view_compiler_styling_spec.ts b/packages/compiler-cli/test/compliance/r3_view_compiler_styling_spec.ts index 21aaf89712..d6b0342c4a 100644 --- a/packages/compiler-cli/test/compliance/r3_view_compiler_styling_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_view_compiler_styling_spec.ts @@ -771,12 +771,16 @@ describe('compiler compliance: styling', () => { const _c0 = ["foo", "baz", ${InitialStylingFlags.VALUES_MODE}, "foo", true, "baz", true]; const _c1 = ["width", "height", "color", ${InitialStylingFlags.VALUES_MODE}, "width", "200px", "height", "500px"]; … - hostBindings: function MyComponent_HostBindings(dirIndex, elIndex) { - $r3$.ɵelementStyling(_c0, _c1, $r3$.ɵdefaultStyleSanitizer, dirIndex); - $r3$.ɵelementStylingMap(elIndex, $r3$.ɵload(dirIndex).myClass, $r3$.ɵload(dirIndex).myStyle, dirIndex); - $r3$.ɵelementStyleProp(elIndex, 2, $r3$.ɵload(dirIndex).myColorProp, null, dirIndex); - $r3$.ɵelementClassProp(elIndex, 0, $r3$.ɵload(dirIndex).myFooClass, dirIndex); - $r3$.ɵelementStylingApply(elIndex, dirIndex); + hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) { + if (rf & 1) { + $r3$.ɵelementStyling(_c0, _c1, $r3$.ɵdefaultStyleSanitizer, ctx); + } + if (rf & 2) { + $r3$.ɵelementStylingMap(elIndex, ctx.myClass, ctx.myStyle, ctx); + $r3$.ɵelementStyleProp(elIndex, 2, ctx.myColorProp, null, ctx); + $r3$.ɵelementClassProp(elIndex, 0, ctx.myFooClass, ctx); + $r3$.ɵelementStylingApply(elIndex, ctx); + } } `; @@ -825,14 +829,18 @@ describe('compiler compliance: styling', () => { const _c0 = ["bar", "foo"]; const _c1 = ["height", "width"]; … - hostBindings: function MyComponent_HostBindings(dirIndex, elIndex) { - $r3$.ɵelementStyling(_c0, _c1, $r3$.ɵdefaultStyleSanitizer, dirIndex); - $r3$.ɵelementStylingMap(elIndex, $r3$.ɵload(dirIndex).myClasses, $r3$.ɵload(dirIndex).myStyle, dirIndex); - $r3$.ɵelementStyleProp(elIndex, 0, $r3$.ɵload(dirIndex).myHeightProp, "pt", dirIndex); - $r3$.ɵelementStyleProp(elIndex, 1, $r3$.ɵload(dirIndex).myWidthProp, null, dirIndex); - $r3$.ɵelementClassProp(elIndex, 0, $r3$.ɵload(dirIndex).myBarClass, dirIndex); - $r3$.ɵelementClassProp(elIndex, 1, $r3$.ɵload(dirIndex).myFooClass, dirIndex); - $r3$.ɵelementStylingApply(elIndex, dirIndex); + hostBindings: function MyComponent_HostBindings(rf, ctx, elIndex) { + if (rf & 1) { + $r3$.ɵelementStyling(_c0, _c1, $r3$.ɵdefaultStyleSanitizer, ctx); + } + if (rf & 2) { + $r3$.ɵelementStylingMap(elIndex, ctx.myClasses, ctx.myStyle, ctx); + $r3$.ɵelementStyleProp(elIndex, 0, ctx.myHeightProp, "pt", ctx); + $r3$.ɵelementStyleProp(elIndex, 1, ctx.myWidthProp, null, ctx); + $r3$.ɵelementClassProp(elIndex, 0, ctx.myBarClass, ctx); + $r3$.ɵelementClassProp(elIndex, 1, ctx.myFooClass, ctx); + $r3$.ɵelementStylingApply(elIndex, ctx); + } } `; @@ -886,18 +894,26 @@ describe('compiler compliance: styling', () => { const _c2 = ["bar"]; const _c3 = ["height"]; … - function WidthDirective_HostBindings(dirIndex, elIndex) { - $r3$.ɵelementStyling(_c0, _c1, null, dirIndex); - $r3$.ɵelementStyleProp(elIndex, 0, $r3$.ɵload(dirIndex).myWidth, null, dirIndex); - $r3$.ɵelementClassProp(elIndex, 0, $r3$.ɵload(dirIndex).myFooClass, dirIndex); - $r3$.ɵelementStylingApply(elIndex, dirIndex); + function WidthDirective_HostBindings(rf, ctx, elIndex) { + if (rf & 1) { + $r3$.ɵelementStyling(_c0, _c1, null, ctx); + } + if (rf & 2) { + $r3$.ɵelementStyleProp(elIndex, 0, ctx.myWidth, null, ctx); + $r3$.ɵelementClassProp(elIndex, 0, ctx.myFooClass, ctx); + $r3$.ɵelementStylingApply(elIndex, ctx); + } } … - function HeightDirective_HostBindings(dirIndex, elIndex) { - $r3$.ɵelementStyling(_c2, _c3, null, dirIndex); - $r3$.ɵelementStyleProp(elIndex, 0, $r3$.ɵload(dirIndex).myHeight, null, dirIndex); - $r3$.ɵelementClassProp(elIndex, 0, $r3$.ɵload(dirIndex).myBarClass, dirIndex); - $r3$.ɵelementStylingApply(elIndex, dirIndex); + function HeightDirective_HostBindings(rf, ctx, elIndex) { + if (rf & 1) { + $r3$.ɵelementStyling(_c2, _c3, null, ctx); + } + if (rf & 2) { + $r3$.ɵelementStyleProp(elIndex, 0, ctx.myHeight, null, ctx); + $r3$.ɵelementClassProp(elIndex, 0, ctx.myBarClass, ctx); + $r3$.ɵelementStylingApply(elIndex, ctx); + } } … `; diff --git a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts index 639838712d..8b159bb2c1 100644 --- a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts +++ b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts @@ -8,6 +8,8 @@ import {NgtscTestEnvironment} from './env'; +const trim = (input: string): string => input.replace(/\s+/g, ' ').trim(); + describe('ngtsc behavioral tests', () => { if (!NgtscTestEnvironment.supported) { // These tests should be excluded from the non-Bazel build. @@ -449,6 +451,37 @@ describe('ngtsc behavioral tests', () => { expect(jsContents).toContain(`i0.ɵquery(null, ViewContainerRef, true)`); }); + it('should generate host listeners for components', () => { + env.tsconfig(); + env.write(`test.ts`, ` + import {Component, HostListener} from '@angular/core'; + + @Component({ + selector: 'test', + template: 'Test' + }) + class FooCmp { + @HostListener('document:click', ['$event.target']) + onClick(eventTarget: HTMLElement): void {} + + @HostListener('window:scroll') + onScroll(event: any): void {} + } + `); + + env.driveMain(); + const jsContents = env.getContents('test.js'); + const hostBindingsFn = ` + hostBindings: function FooCmp_HostBindings(rf, ctx, elIndex) { + if (rf & 1) { + i0.ɵlistener("click", function FooCmp_click_HostBindingHandler($event) { return ctx.onClick($event.target); }); + i0.ɵlistener("scroll", function FooCmp_scroll_HostBindingHandler($event) { return ctx.onScroll(); }); + } + } + `; + expect(trim(jsContents)).toContain(trim(hostBindingsFn)); + }); + it('should generate host bindings for directives', () => { env.tsconfig(); env.write(`test.ts`, ` @@ -476,39 +509,33 @@ describe('ngtsc behavioral tests', () => { env.driveMain(); const jsContents = env.getContents('test.js'); - expect(jsContents) - .toContain(`i0.ɵelementAttribute(elIndex, "hello", i0.ɵbind(i0.ɵload(dirIndex).foo));`); - expect(jsContents) - .toContain(`i0.ɵelementProperty(elIndex, "prop", i0.ɵbind(i0.ɵload(dirIndex).bar));`); - expect(jsContents) - .toContain('i0.ɵelementClassProp(elIndex, 0, i0.ɵload(dirIndex).someClass, dirIndex)'); - - const factoryDef = ` - factory: function FooCmp_Factory(t) { - var f = new (t || FooCmp)(); - i0.ɵlistener("click", function FooCmp_click_HostBindingHandler($event) { - return f.onClick($event); - }); - i0.ɵlistener("change", function FooCmp_change_HostBindingHandler($event) { - return f.onChange(f.arg1, f.arg2, f.arg3); - }); - return f; + const hostBindingsFn = ` + hostBindings: function FooCmp_HostBindings(rf, ctx, elIndex) { + if (rf & 1) { + i0.ɵlistener("click", function FooCmp_click_HostBindingHandler($event) { return ctx.onClick($event); }); + i0.ɵlistener("change", function FooCmp_change_HostBindingHandler($event) { return ctx.onChange(ctx.arg1, ctx.arg2, ctx.arg3); }); + i0.ɵelementStyling(_c0, null, null, ctx); + } + if (rf & 2) { + i0.ɵelementAttribute(elIndex, "hello", i0.ɵbind(ctx.foo)); + i0.ɵelementProperty(elIndex, "prop", i0.ɵbind(ctx.bar)); + i0.ɵelementClassProp(elIndex, 0, ctx.someClass, ctx); + i0.ɵelementStylingApply(elIndex, ctx); + } } `; - expect(jsContents).toContain(factoryDef.replace(/\s+/g, ' ').trim()); + expect(trim(jsContents)).toContain(trim(hostBindingsFn)); }); - it('should generate host listeners for directives with base factories', () => { + it('should generate host listeners for directives within hostBindings section', () => { env.tsconfig(); env.write(`test.ts`, ` import {Directive, HostListener} from '@angular/core'; - class Base {} - @Directive({ selector: '[test]', }) - class Dir extends Base { + class Dir { @HostListener('change', ['arg']) onChange(event: any, arg: any): void {} } @@ -516,17 +543,14 @@ describe('ngtsc behavioral tests', () => { env.driveMain(); const jsContents = env.getContents('test.js'); - const factoryDef = ` - factory: function Dir_Factory(t) { - var f = ɵDir_BaseFactory((t || Dir)); - i0.ɵlistener("change", function Dir_change_HostBindingHandler($event) { - return f.onChange(f.arg); - }); - return f; + const hostBindingsFn = ` + hostBindings: function Dir_HostBindings(rf, ctx, elIndex) { + if (rf & 1) { + i0.ɵlistener("change", function Dir_change_HostBindingHandler($event) { return ctx.onChange(ctx.arg); }); + } } `; - expect(jsContents).toContain(factoryDef.replace(/\s+/g, ' ').trim()); - expect(jsContents).toContain('var ɵDir_BaseFactory = i0.ɵgetInheritedFactory(Dir)'); + expect(trim(jsContents)).toContain(trim(hostBindingsFn)); }); it('should correctly recognize local symbols', () => { diff --git a/packages/compiler/src/injectable_compiler_2.ts b/packages/compiler/src/injectable_compiler_2.ts index bb82a5ffd4..e2b5845254 100644 --- a/packages/compiler/src/injectable_compiler_2.ts +++ b/packages/compiler/src/injectable_compiler_2.ts @@ -42,7 +42,6 @@ export function compileInjectable(meta: R3InjectableMetadata): InjectableDef { type: meta.type, deps: meta.ctorDeps, injectFn: Identifiers.inject, - extraStatementFn: null, }; if (meta.useClass !== undefined) { diff --git a/packages/compiler/src/render3/r3_factory.ts b/packages/compiler/src/render3/r3_factory.ts index 2b605d5c5d..d1f0a8a5e4 100644 --- a/packages/compiler/src/render3/r3_factory.ts +++ b/packages/compiler/src/render3/r3_factory.ts @@ -49,11 +49,6 @@ export interface R3ConstructorFactoryMetadata { * function could be different, and other options control how it will be invoked. */ injectFn: o.ExternalReference; - - /** - * Function that allows extra statements to be inserted into factory function. - */ - extraStatementFn: ((instance: o.Expression) => o.Statement[])|null; } export enum R3FactoryDelegateType { @@ -208,22 +203,10 @@ export function compileFactoryFunction(meta: R3FactoryMetadata): } else if (isExpressionFactoryMetadata(meta)) { // TODO(alxhub): decide whether to lower the value here or in the caller retExpr = makeConditionalFactory(meta.expression); - } else if (meta.extraStatementFn) { - // if extraStatementsFn is specified and the 'makeConditionalFactory' function - // was not invoked, we need to create a reference to the instance, so we can - // pass it as an argument to the 'extraStatementFn' function while calling it - const variable = o.variable('f'); - body.push(variable.set(ctorExpr).toDeclStmt()); - retExpr = variable; } else { retExpr = ctorExpr; } - if (meta.extraStatementFn) { - const extraStmts = meta.extraStatementFn(retExpr); - body.push(...extraStmts); - } - return { factory: o.fn( [new o.FnParam('t', o.DYNAMIC_TYPE)], [...body, new o.ReturnStatement(retExpr)], diff --git a/packages/compiler/src/render3/r3_module_compiler.ts b/packages/compiler/src/render3/r3_module_compiler.ts index 0ec1b38555..fa6020a411 100644 --- a/packages/compiler/src/render3/r3_module_compiler.ts +++ b/packages/compiler/src/render3/r3_module_compiler.ts @@ -101,7 +101,6 @@ export function compileInjector(meta: R3InjectorMetadata): R3InjectorDef { type: meta.type, deps: meta.deps, injectFn: R3.inject, - extraStatementFn: null, }); const expression = o.importExpr(R3.defineInjector).callFn([mapToMapExpression({ factory: result.factory, diff --git a/packages/compiler/src/render3/r3_pipe_compiler.ts b/packages/compiler/src/render3/r3_pipe_compiler.ts index c3df986fe9..2e35262710 100644 --- a/packages/compiler/src/render3/r3_pipe_compiler.ts +++ b/packages/compiler/src/render3/r3_pipe_compiler.ts @@ -43,7 +43,6 @@ export function compilePipeFromMetadata(metadata: R3PipeMetadata) { type: metadata.type, deps: metadata.deps, injectFn: R3.directiveInject, - extraStatementFn: null, }); definitionMapValues.push({key: 'factory', value: templateFactory.factory, quoted: false}); diff --git a/packages/compiler/src/render3/view/compiler.ts b/packages/compiler/src/render3/view/compiler.ts index fc85fa22cd..0f962932cf 100644 --- a/packages/compiler/src/render3/view/compiler.ts +++ b/packages/compiler/src/render3/view/compiler.ts @@ -55,7 +55,6 @@ function baseDirectiveFields( type: meta.type, deps: meta.deps, injectFn: R3.directiveInject, - extraStatementFn: createFactoryExtraStatementsFn(meta, bindingParser) }); definitionMap.set('factory', result.factory); @@ -67,8 +66,8 @@ function baseDirectiveFields( let hostVars = Object.keys(meta.host.properties).length; const elVarExp = o.variable('elIndex'); - const dirVarExp = o.variable('dirIndex'); - const styleBuilder = new StylingBuilder(elVarExp, dirVarExp); + const contextVarExp = o.variable(CONTEXT_NAME); + const styleBuilder = new StylingBuilder(elVarExp, contextVarExp); const allOtherAttributes: any = {}; const attrNames = Object.getOwnPropertyNames(meta.host.attributes); @@ -93,15 +92,15 @@ function baseDirectiveFields( // e.g. `attributes: ['role', 'listbox']` definitionMap.set('attributes', createHostAttributesArray(allOtherAttributes)); - // e.g. `hostBindings: (dirIndex, elIndex) => { ... } + // e.g. `hostBindings: (rf, ctx, elIndex) => { ... } definitionMap.set( - 'hostBindings', - createHostBindingsFunction( - meta, elVarExp, dirVarExp, styleBuilder, bindingParser, constantPool, (slots: number) => { - const originalSlots = hostVars; - hostVars += slots; - return originalSlots; - })); + 'hostBindings', createHostBindingsFunction( + meta, elVarExp, contextVarExp, styleBuilder, bindingParser, constantPool, + (slots: number) => { + const originalSlots = hostVars; + hostVars += slots; + return originalSlots; + })); if (hostVars) { // e.g. `hostVars: 2 @@ -643,18 +642,26 @@ function createViewQueriesFunction( // Return a host binding function or null if one is not necessary. function createHostBindingsFunction( - meta: R3DirectiveMetadata, elVarExp: o.ReadVarExpr, dirVarExp: o.ReadVarExpr, + meta: R3DirectiveMetadata, elVarExp: o.ReadVarExpr, bindingContext: o.ReadVarExpr, styleBuilder: StylingBuilder, bindingParser: BindingParser, constantPool: ConstantPool, allocatePureFunctionSlots: (slots: number) => number): o.Expression|null { - const statements: o.Statement[] = []; + const createStatements: o.Statement[] = []; + const updateStatements: o.Statement[] = []; const hostBindingSourceSpan = meta.typeSourceSpan; const directiveSummary = metadataAsSummary(meta); + // Calculate host event bindings + const eventBindings = + bindingParser.createDirectiveHostEventAsts(directiveSummary, hostBindingSourceSpan); + if (eventBindings && eventBindings.length) { + const listeners = createHostListeners(bindingContext, eventBindings, meta); + createStatements.push(...listeners); + } + // Calculate the host property bindings const bindings = bindingParser.createBoundHostProperties(directiveSummary, hostBindingSourceSpan); - const bindingContext = o.importExpr(R3.load).callFn([dirVarExp]); const bindingFn = (implicit: any, value: AST) => { return convertPropertyBinding( @@ -683,14 +690,14 @@ function createHostBindingsFunction( const {bindingName, instruction} = getBindingNameAndInstruction(name); - statements.push(...bindingExpr.stmts); - statements.push(o.importExpr(instruction) - .callFn([ - elVarExp, - o.literal(bindingName), - o.importExpr(R3.bind).callFn([bindingExpr.currValExpr]), - ]) - .toStmt()); + updateStatements.push(...bindingExpr.stmts); + updateStatements.push(o.importExpr(instruction) + .callFn([ + elVarExp, + o.literal(bindingName), + o.importExpr(R3.bind).callFn([bindingExpr.currValExpr]), + ]) + .toStmt()); } } @@ -698,24 +705,31 @@ function createHostBindingsFunction( const createInstruction = styleBuilder.buildCreateLevelInstruction(null, constantPool); if (createInstruction) { const createStmt = createStylingStmt(createInstruction, bindingContext, bindingFn); - statements.push(createStmt); + createStatements.push(createStmt); } styleBuilder.buildUpdateLevelInstructions(valueConverter).forEach(instruction => { const updateStmt = createStylingStmt(instruction, bindingContext, bindingFn); - statements.push(updateStmt); + updateStatements.push(updateStmt); }); } } - if (statements.length > 0) { - const typeName = meta.name; + if (createStatements.length > 0 || updateStatements.length > 0) { + const hostBindingsFnName = meta.name ? `${meta.name}_HostBindings` : null; + const statements: o.Statement[] = []; + if (createStatements.length > 0) { + statements.push(renderFlagCheckIfStmt(core.RenderFlags.Create, createStatements)); + } + if (updateStatements.length > 0) { + statements.push(renderFlagCheckIfStmt(core.RenderFlags.Update, updateStatements)); + } return o.fn( [ - new o.FnParam(dirVarExp.name !, o.NUMBER_TYPE), - new o.FnParam(elVarExp.name !, o.NUMBER_TYPE), + new o.FnParam(RENDER_FLAGS, o.NUMBER_TYPE), new o.FnParam(CONTEXT_NAME, null), + new o.FnParam(elVarExp.name !, o.NUMBER_TYPE) ], - statements, o.INFERRED_TYPE, null, typeName ? `${typeName}_HostBindings` : null); + statements, o.INFERRED_TYPE, null, hostBindingsFnName); } return null; @@ -745,15 +759,6 @@ function getBindingNameAndInstruction(bindingName: string): return {bindingName, instruction}; } -function createFactoryExtraStatementsFn(meta: R3DirectiveMetadata, bindingParser: BindingParser): - ((instance: o.Expression) => o.Statement[])|null { - const eventBindings = - bindingParser.createDirectiveHostEventAsts(metadataAsSummary(meta), meta.typeSourceSpan); - return eventBindings && eventBindings.length ? - (instance: o.Expression) => createHostListeners(instance, eventBindings, meta) : - null; -} - function createHostListeners( bindingContext: o.Expression, eventBindings: ParsedEvent[], meta: R3DirectiveMetadata): o.Statement[] { diff --git a/packages/compiler/src/render3/view/styling.ts b/packages/compiler/src/render3/view/styling.ts index 98cdbbc821..5b374fe22e 100644 --- a/packages/compiler/src/render3/view/styling.ts +++ b/packages/compiler/src/render3/view/styling.ts @@ -80,8 +80,7 @@ export class StylingBuilder { private _useDefaultSanitizer = false; private _applyFnRequired = false; - constructor( - private _elementIndexExpr: o.Expression, private _directiveIndexExpr: o.Expression|null) {} + constructor(private _elementIndexExpr: o.Expression, private _directiveExpr: o.Expression|null) {} registerBoundInput(input: t.BoundAttribute): boolean { // [attr.style] or [attr.class] are skipped in the code below, @@ -217,15 +216,15 @@ export class StylingBuilder { // can be processed during runtime. These initial styles values are bound to // a constant because the inital style values do not change (since they're static). params.push(constantPool.getConstLiteral(initialStyles, true)); - } else if (useSanitizer || this._directiveIndexExpr) { + } else if (useSanitizer || this._directiveExpr) { // no point in having an extra `null` value unless there are follow-up params params.push(o.NULL_EXPR); } - if (useSanitizer || this._directiveIndexExpr) { + if (useSanitizer || this._directiveExpr) { params.push(useSanitizer ? o.importExpr(R3.defaultStyleSanitizer) : o.NULL_EXPR); - if (this._directiveIndexExpr) { - params.push(this._directiveIndexExpr); + if (this._directiveExpr) { + params.push(this._directiveExpr); } } @@ -260,12 +259,12 @@ export class StylingBuilder { if (mapBasedStyleValue) { params.push(convertFn(mapBasedStyleValue)); - } else if (this._directiveIndexExpr) { + } else if (this._directiveExpr) { params.push(o.NULL_EXPR); } - if (this._directiveIndexExpr) { - params.push(this._directiveIndexExpr); + if (this._directiveExpr) { + params.push(this._directiveExpr); } return params; @@ -289,13 +288,13 @@ export class StylingBuilder { if (allowUnits) { if (input.unit) { params.push(o.literal(input.unit)); - } else if (this._directiveIndexExpr) { + } else if (this._directiveExpr) { params.push(o.NULL_EXPR); } } - if (this._directiveIndexExpr) { - params.push(this._directiveIndexExpr); + if (this._directiveExpr) { + params.push(this._directiveExpr); } return params; } @@ -325,8 +324,8 @@ export class StylingBuilder { reference: R3.elementStylingApply, buildParams: () => { const params: o.Expression[] = [this._elementIndexExpr]; - if (this._directiveIndexExpr) { - params.push(this._directiveIndexExpr); + if (this._directiveExpr) { + params.push(this._directiveExpr); } return params; } diff --git a/packages/core/src/render3/definition.ts b/packages/core/src/render3/definition.ts index 627a05d102..f4a29f756e 100644 --- a/packages/core/src/render3/definition.ts +++ b/packages/core/src/render3/definition.ts @@ -16,7 +16,7 @@ import {Mutable, Type} from '../type'; import {noSideEffects} from '../util'; import {NG_COMPONENT_DEF, NG_DIRECTIVE_DEF, NG_MODULE_DEF, NG_PIPE_DEF} from './fields'; -import {BaseDef, ComponentDef, ComponentDefFeature, ComponentQuery, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFeature, DirectiveType, DirectiveTypesOrFactory, PipeDef, PipeType, PipeTypesOrFactory} from './interfaces/definition'; +import {BaseDef, ComponentDef, ComponentDefFeature, ComponentQuery, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFeature, DirectiveType, DirectiveTypesOrFactory, HostBindingsFunction, PipeDef, PipeType, PipeTypesOrFactory} from './interfaces/definition'; import {CssSelectorList, SelectorFlags} from './interfaces/projection'; export const EMPTY: {} = {}; @@ -149,7 +149,7 @@ export function defineComponent(componentDefinition: { /** * Function executed by the parent template to allow child directive to apply host bindings. */ - hostBindings?: (directiveIndex: number, elementIndex: number) => void; + hostBindings?: HostBindingsFunction; /** * Function to create instances of content queries associated with a given directive. @@ -597,7 +597,7 @@ export const defineDirective = defineComponent as any as(directiveDefinition: /** * Function executed by the parent template to allow child directive to apply host bindings. */ - hostBindings?: (directiveIndex: number, elementIndex: number) => void; + hostBindings?: HostBindingsFunction; /** * Function to create instances of content queries associated with a given directive. diff --git a/packages/core/src/render3/features/inherit_definition_feature.ts b/packages/core/src/render3/features/inherit_definition_feature.ts index 4a9de9835d..1b53cb641e 100644 --- a/packages/core/src/render3/features/inherit_definition_feature.ts +++ b/packages/core/src/render3/features/inherit_definition_feature.ts @@ -72,9 +72,9 @@ export function InheritDefinitionFeature(definition: DirectiveDef| Componen const superHostBindings = superDef.hostBindings; if (superHostBindings) { if (prevHostBindings) { - definition.hostBindings = (directiveIndex: number, elementIndex: number) => { - superHostBindings(directiveIndex, elementIndex); - prevHostBindings(directiveIndex, elementIndex); + definition.hostBindings = (rf: RenderFlags, ctx: any, elementIndex: number) => { + superHostBindings(rf, ctx, elementIndex); + prevHostBindings(rf, ctx, elementIndex); }; (definition as any).hostVars += superDef.hostVars; } else { diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index b7d9aefd5b..24320126cd 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -124,15 +124,9 @@ export function setHostBindings(tView: TView, viewData: LViewData): void { } else { // If it's not a number, it's a host binding function that needs to be executed. viewData[BINDING_INDEX] = bindingRootIndex; - // We must subtract the header offset because the load() instruction - // expects a raw, unadjusted index. - // : set the `previousOrParentTNode` so that hostBindings functions can - // correctly retrieve it. This should be removed once we call the hostBindings function - // inline as part of the `RenderFlags.Create` because in that case the value will already be - // correctly set. - setPreviousOrParentTNode(getTView().data[currentElementIndex + HEADER_OFFSET] as TNode); - // - instruction(currentDirectiveIndex - HEADER_OFFSET, currentElementIndex); + instruction( + RenderFlags.Update, readElementValue(viewData[currentDirectiveIndex]), + currentElementIndex); currentDirectiveIndex++; } } @@ -1077,14 +1071,13 @@ function generatePropertyAliases( * @param classIndex Index of class to toggle. Because it is going to DOM, this is not subject to * renaming as part of minification. * @param value A value indicating if a given class should be added or removed. - * @param directiveIndex the index for the directive that is attempting to change styling. + * @param directive the ref to the directive that is attempting to change styling. */ export function elementClassProp( - index: number, classIndex: number, value: boolean | PlayerFactory, - directiveIndex?: number): void { - if (directiveIndex != undefined) { + index: number, classIndex: number, value: boolean | PlayerFactory, directive?: {}): void { + if (directive != undefined) { return hackImplementationOfElementClassProp( - index, classIndex, value, directiveIndex); // proper supported in next PR + index, classIndex, value, directive); // proper supported in next PR } const val = (value instanceof BoundPlayerFactory) ? (value as BoundPlayerFactory) : (!!value); @@ -1118,17 +1111,17 @@ export function elementClassProp( * values that are passed in here will be applied to the element (if matched). * @param styleSanitizer An optional sanitizer function that will be used (if provided) * to sanitize the any CSS property values that are applied to the element (during rendering). - * @param directiveIndex the index for the directive that is attempting to change styling. + * @param directive the ref to the directive that is attempting to change styling. */ export function elementStyling( classDeclarations?: (string | boolean | InitialStylingFlags)[] | null, styleDeclarations?: (string | boolean | InitialStylingFlags)[] | null, - styleSanitizer?: StyleSanitizeFn | null, directiveIndex?: number): void { - if (directiveIndex !== undefined) { + styleSanitizer?: StyleSanitizeFn | null, directive?: {}): void { + if (directive != undefined) { getCreationMode() && hackImplementationOfElementStyling( classDeclarations || null, styleDeclarations || null, styleSanitizer || null, - directiveIndex); // supported in next PR + directive); // supported in next PR return; } const tNode = getPreviousOrParentTNode(); @@ -1171,11 +1164,11 @@ export function elementStyling( * (Note that this is not the element index, but rather an index value allocated * specifically for element styling--the index must be the next index after the element * index.) - * @param directiveIndex the index for the directive that is attempting to change styling. + * @param directive the ref to the directive that is attempting to change styling. */ -export function elementStylingApply(index: number, directiveIndex?: number): void { - if (directiveIndex != undefined) { - return hackImplementationOfElementStylingApply(index, directiveIndex); // supported in next PR +export function elementStylingApply(index: number, directive?: {}): void { + if (directive != undefined) { + return hackImplementationOfElementStylingApply(index, directive); // supported in next PR } const viewData = getViewData(); const isFirstRender = (viewData[FLAGS] & LViewFlags.CreationMode) !== 0; @@ -1206,14 +1199,14 @@ export function elementStylingApply(index: number, directiveIndex?: number): voi * @param suffix Optional suffix. Used with scalar values to add unit such as `px`. * Note that when a suffix is provided then the underlying sanitizer will * be ignored. - * @param directiveIndex the index for the directive that is attempting to change styling. + * @param directive the ref to the directive that is attempting to change styling. */ export function elementStyleProp( index: number, styleIndex: number, value: string | number | String | PlayerFactory | null, - suffix?: string, directiveIndex?: number): void { - if (directiveIndex != undefined) + suffix?: string, directive?: {}): void { + if (directive != undefined) return hackImplementationOfElementStyleProp( - index, styleIndex, value, suffix, directiveIndex); // supported in next PR + index, styleIndex, value, suffix, directive); // supported in next PR let valueToAdd: string|null = null; if (value) { if (suffix) { @@ -1251,14 +1244,14 @@ export function elementStyleProp( * @param styles A key/value style map of the styles that will be applied to the given element. * Any missing styles (that have already been applied to the element beforehand) will be * removed (unset) from the element's styling. - * @param directiveIndex the index for the directive that is attempting to change styling. + * @param directive the ref to the directive that is attempting to change styling. */ export function elementStylingMap( index: number, classes: {[key: string]: any} | string | NO_CHANGE | null, - styles?: {[styleName: string]: any} | NO_CHANGE | null, directiveIndex?: number): void { - if (directiveIndex != undefined) + styles?: {[styleName: string]: any} | NO_CHANGE | null, directive?: {}): void { + if (directive != undefined) return hackImplementationOfElementStylingMap( - index, classes, styles, directiveIndex); // supported in next PR + index, classes, styles, directive); // supported in next PR const viewData = getViewData(); const tNode = getTNode(index, viewData); const stylingContext = getStylingContext(index, viewData); @@ -1282,22 +1275,20 @@ interface HostStylingHack { styleDeclarations: string[]; styleSanitizer: StyleSanitizeFn|null; } -interface HostStylingHackMap { - [directiveIndex: number]: HostStylingHack; -} +type HostStylingHackMap = Map<{}, HostStylingHack>; function hackImplementationOfElementStyling( classDeclarations: (string | boolean | InitialStylingFlags)[] | null, styleDeclarations: (string | boolean | InitialStylingFlags)[] | null, - styleSanitizer: StyleSanitizeFn | null, directiveIndex: number): void { + styleSanitizer: StyleSanitizeFn | null, directive: {}): void { const node = getNativeByTNode(getPreviousOrParentTNode(), getViewData()); ngDevMode && assertDefined(node, 'expecting parent DOM node'); const hostStylingHackMap: HostStylingHackMap = - ((node as any).hostStylingHack || ((node as any).hostStylingHack = {})); - hostStylingHackMap[directiveIndex] = { + ((node as any).hostStylingHack || ((node as any).hostStylingHack = new Map())); + hostStylingHackMap.set(directive, { classDeclarations: hackSquashDeclaration(classDeclarations), styleDeclarations: hackSquashDeclaration(styleDeclarations), styleSanitizer - }; + }); } function hackSquashDeclaration(declarations: (string | boolean | InitialStylingFlags)[] | null): @@ -1307,11 +1298,10 @@ function hackSquashDeclaration(declarations: (string | boolean | InitialStylingF } function hackImplementationOfElementClassProp( - index: number, classIndex: number, value: boolean | PlayerFactory, - directiveIndex: number): void { + index: number, classIndex: number, value: boolean | PlayerFactory, directive: {}): void { const node = getNativeByIndex(index, getViewData()); ngDevMode && assertDefined(node, 'could not locate node'); - const hostStylingHack: HostStylingHack = (node as any).hostStylingHack[directiveIndex]; + const hostStylingHack: HostStylingHack = (node as any).hostStylingHack.get(directive); const className = hostStylingHack.classDeclarations[classIndex]; const renderer = getRenderer(); if (isProceduralRenderer(renderer)) { @@ -1322,19 +1312,19 @@ function hackImplementationOfElementClassProp( } } -function hackImplementationOfElementStylingApply(index: number, directiveIndex?: number): void { +function hackImplementationOfElementStylingApply(index: number, directive?: {}): void { // Do nothing because the hack implementation is eager. } function hackImplementationOfElementStyleProp( index: number, styleIndex: number, value: string | number | String | PlayerFactory | null, - suffix?: string, directiveIndex?: number): void { + suffix?: string, directive?: {}): void { throw new Error('unimplemented. Should not be needed by ViewEngine compatibility'); } function hackImplementationOfElementStylingMap( index: number, classes: {[key: string]: any} | string | NO_CHANGE | null, - styles?: {[styleName: string]: any} | NO_CHANGE | null, directiveIndex?: number): void { + styles?: {[styleName: string]: any} | NO_CHANGE | null, directive?: {}): void { throw new Error('unimplemented. Should not be needed by ViewEngine compatibility'); } @@ -1531,6 +1521,10 @@ function postProcessBaseDirective( 'directives should be created before any bindings'); ngDevMode && assertPreviousIsParent(); + if (def.hostBindings) { + def.hostBindings(RenderFlags.Create, directive, previousOrParentTNode.index); + } + attachPatchData(directive, viewData); if (native) { attachPatchData(native, viewData); diff --git a/packages/core/src/render3/interfaces/definition.ts b/packages/core/src/render3/interfaces/definition.ts index 749886e02e..2b89774ce6 100644 --- a/packages/core/src/render3/interfaces/definition.ts +++ b/packages/core/src/render3/interfaces/definition.ts @@ -145,7 +145,7 @@ export interface DirectiveDef extends BaseDef { readonly hostVars: number; /** Refreshes host bindings on the associated directive. */ - hostBindings: HostBindingsFunction|null; + hostBindings: HostBindingsFunction|null; /** * Static attributes to set on host element. @@ -333,7 +333,7 @@ export type DirectiveTypeList = (DirectiveDef| ComponentDef| Type/* Type as workaround for: Microsoft/TypeScript/issues/4881 */)[]; -export type HostBindingsFunction = (directiveIndex: number, elementIndex: number) => void; +export type HostBindingsFunction = (rf: RenderFlags, ctx: T, elementIndex: number) => void; /** * Type used for PipeDefs on component definition. diff --git a/packages/core/src/render3/interfaces/view.ts b/packages/core/src/render3/interfaces/view.ts index 64e7d32a82..f578b3b736 100644 --- a/packages/core/src/render3/interfaces/view.ts +++ b/packages/core/src/render3/interfaces/view.ts @@ -342,7 +342,7 @@ export interface TView { * * See VIEW_DATA.md for more information. */ - expandoInstructions: (number|HostBindingsFunction)[]|null; + expandoInstructions: (number|HostBindingsFunction)[]|null; /** * Full registry of directives and components that may be found in this view. diff --git a/packages/core/test/render3/Inherit_definition_feature_spec.ts b/packages/core/test/render3/Inherit_definition_feature_spec.ts index 2de9281c64..fc89cf7884 100644 --- a/packages/core/test/render3/Inherit_definition_feature_spec.ts +++ b/packages/core/test/render3/Inherit_definition_feature_spec.ts @@ -308,9 +308,10 @@ describe('InheritDefinitionFeature', () => { static ngDirectiveDef = defineDirective({ type: SuperDirective, selectors: [['', 'superDir', '']], - hostBindings: (directiveIndex: number, elementIndex: number) => { - const instance = load(directiveIndex) as SuperDirective; - elementProperty(elementIndex, 'id', bind(instance.id)); + hostBindings: (rf: RenderFlags, ctx: SuperDirective, elementIndex: number) => { + if (rf & RenderFlags.Update) { + elementProperty(elementIndex, 'id', bind(ctx.id)); + } }, hostVars: 1, factory: () => new SuperDirective(), @@ -323,9 +324,10 @@ describe('InheritDefinitionFeature', () => { static ngDirectiveDef = defineDirective({ type: SubDirective, selectors: [['', 'subDir', '']], - hostBindings: (directiveIndex: number, elementIndex: number) => { - const instance = load(directiveIndex) as SubDirective; - elementProperty(elementIndex, 'title', bind(instance.title)); + hostBindings: (rf: RenderFlags, ctx: SubDirective, elementIndex: number) => { + if (rf & RenderFlags.Update) { + elementProperty(elementIndex, 'title', bind(ctx.title)); + } }, hostVars: 1, factory: () => subDir = new SubDirective(), diff --git a/packages/core/test/render3/di_spec.ts b/packages/core/test/render3/di_spec.ts index 2b7648c017..2fc954a465 100644 --- a/packages/core/test/render3/di_spec.ts +++ b/packages/core/test/render3/di_spec.ts @@ -655,8 +655,10 @@ describe('di', () => { selectors: [['', 'hostBindingDir', '']], factory: () => hostBindingDir = new HostBindingDir(), hostVars: 1, - hostBindings: (directiveIndex: number, elementIndex: number) => { - elementProperty(elementIndex, 'id', bind(load(directiveIndex).id)); + hostBindings: (rf: RenderFlags, ctx: any, elementIndex: number) => { + if (rf & RenderFlags.Update) { + elementProperty(elementIndex, 'id', bind(ctx.id)); + } } }); } diff --git a/packages/core/test/render3/host_binding_spec.ts b/packages/core/test/render3/host_binding_spec.ts index a3e5d12f62..73e7653f36 100644 --- a/packages/core/test/render3/host_binding_spec.ts +++ b/packages/core/test/render3/host_binding_spec.ts @@ -50,8 +50,10 @@ describe('host bindings', () => { selectors: [['', 'hostBindingDir', '']], factory: () => hostBindingDir = new HostBindingDir(), hostVars: 1, - hostBindings: (directiveIndex: number, elementIndex: number) => { - elementProperty(elementIndex, 'id', bind(load(directiveIndex).id)); + hostBindings: (rf: RenderFlags, ctx: any, elementIndex: number) => { + if (rf & RenderFlags.Update) { + elementProperty(elementIndex, 'id', bind(ctx.id)); + } } }); } @@ -67,9 +69,10 @@ describe('host bindings', () => { consts: 0, vars: 0, hostVars: 1, - hostBindings: (dirIndex: number, elIndex: number) => { - const ctx = load(dirIndex) as HostBindingComp; - elementProperty(elIndex, 'id', bind(ctx.id)); + hostBindings: (rf: RenderFlags, ctx: HostBindingComp, elIndex: number) => { + if (rf & RenderFlags.Update) { + elementProperty(elIndex, 'id', bind(ctx.id)); + } }, template: (rf: RenderFlags, ctx: HostBindingComp) => {} }); @@ -87,8 +90,10 @@ describe('host bindings', () => { selectors: [['', 'dir', '']], factory: () => directiveInstance = new Directive, hostVars: 1, - hostBindings: (directiveIndex: number, elementIndex: number) => { - elementProperty(elementIndex, 'className', bind(load(directiveIndex).klass)); + hostBindings: (rf: RenderFlags, ctx: any, elementIndex: number) => { + if (rf & RenderFlags.Update) { + elementProperty(elementIndex, 'className', bind(ctx.klass)); + } } }); } @@ -135,9 +140,10 @@ describe('host bindings', () => { consts: 0, vars: 0, hostVars: 1, - hostBindings: (dirIndex: number, elIndex: number) => { - const instance = load(dirIndex) as CompWithProviders; - elementProperty(elIndex, 'id', bind(instance.id)); + hostBindings: (rf: RenderFlags, ctx: CompWithProviders, elIndex: number) => { + if (rf & RenderFlags.Update) { + elementProperty(elIndex, 'id', bind(ctx.id)); + } }, template: (rf: RenderFlags, ctx: CompWithProviders) => {}, features: [ProvidersFeature([[ServiceOne], [ServiceTwo]])] @@ -168,9 +174,10 @@ describe('host bindings', () => { consts: 0, vars: 0, hostVars: 1, - hostBindings: (dirIndex: number, elIndex: number) => { - const ctx = load(dirIndex) as HostTitleComp; - elementProperty(elIndex, 'title', bind(ctx.title)); + hostBindings: (rf: RenderFlags, ctx: HostTitleComp, elIndex: number) => { + if (rf & RenderFlags.Update) { + elementProperty(elIndex, 'title', bind(ctx.title)); + } }, template: (rf: RenderFlags, ctx: HostTitleComp) => {} }); @@ -248,9 +255,10 @@ describe('host bindings', () => { vars: 0, hostVars: 1, features: [NgOnChangesFeature], - hostBindings: (dirIndex: number, elIndex: number) => { - const ctx = load(dirIndex) as InitHookComp; - elementProperty(elIndex, 'title', bind(ctx.value)); + hostBindings: (rf: RenderFlags, ctx: InitHookComp, elIndex: number) => { + if (rf & RenderFlags.Update) { + elementProperty(elIndex, 'title', bind(ctx.value)); + } }, inputs: {inputValue: 'inputValue'} }); @@ -413,12 +421,14 @@ describe('host bindings', () => { consts: 0, vars: 0, hostVars: 8, - hostBindings: (dirIndex: number, elIndex: number) => { - const ctx = load(dirIndex) as HostBindingComp; + hostBindings: (rf: RenderFlags, ctx: HostBindingComp, elIndex: number) => { // LViewData: [..., id, dir, title, ctx.id, pf1, ctx.title, ctx.otherTitle, pf2] - elementProperty(elIndex, 'id', bind(pureFunction1(3, ff, ctx.id))); - elementProperty(elIndex, 'dir', bind(ctx.dir)); - elementProperty(elIndex, 'title', bind(pureFunction2(5, ff2, ctx.title, ctx.otherTitle))); + if (rf & RenderFlags.Update) { + elementProperty(elIndex, 'id', bind(pureFunction1(3, ff, ctx.id))); + elementProperty(elIndex, 'dir', bind(ctx.dir)); + elementProperty( + elIndex, 'title', bind(pureFunction2(5, ff2, ctx.title, ctx.otherTitle))); + } }, template: (rf: RenderFlags, ctx: HostBindingComp) => {} }); @@ -487,10 +497,11 @@ describe('host bindings', () => { consts: 0, vars: 0, hostVars: 3, - hostBindings: (dirIndex: number, elIndex: number) => { + hostBindings: (rf: RenderFlags, ctx: HostBindingComp, elIndex: number) => { // LViewData: [..., id, ctx.id, pf1] - const ctx = load(dirIndex) as HostBindingComp; - elementProperty(elIndex, 'id', bind(pureFunction1(1, ff, ctx.id))); + if (rf & RenderFlags.Update) { + elementProperty(elIndex, 'id', bind(pureFunction1(1, ff, ctx.id))); + } }, template: (rf: RenderFlags, ctx: HostBindingComp) => {} }); @@ -515,10 +526,11 @@ describe('host bindings', () => { selectors: [['', 'hostDir', '']], factory: () => hostBindingDir = new HostBindingDir(), hostVars: 3, - hostBindings: (dirIndex: number, elIndex: number) => { + hostBindings: (rf: RenderFlags, ctx: HostBindingDir, elIndex: number) => { // LViewData [..., title, ctx.title, pf1] - const ctx = load(dirIndex) as HostBindingDir; - elementProperty(elIndex, 'title', bind(pureFunction1(1, ff1, ctx.title))); + if (rf & RenderFlags.Update) { + elementProperty(elIndex, 'title', bind(pureFunction1(1, ff1, ctx.title))); + } } }); } @@ -576,14 +588,15 @@ describe('host bindings', () => { consts: 0, vars: 0, hostVars: 6, - hostBindings: (dirIndex: number, elIndex: number) => { + hostBindings: (rf: RenderFlags, ctx: HostBindingComp, elIndex: number) => { // LViewData: [..., id, title, ctx.id, pf1, ctx.title, pf1] - const ctx = load(dirIndex) as HostBindingComp; - elementProperty( - elIndex, 'id', bind(ctx.condition ? pureFunction1(2, ff, ctx.id) : 'green')); - elementProperty( - elIndex, 'title', - bind(ctx.otherCondition ? pureFunction1(4, ff1, ctx.title) : 'other title')); + if (rf & RenderFlags.Update) { + elementProperty( + elIndex, 'id', bind(ctx.condition ? pureFunction1(2, ff, ctx.id) : 'green')); + elementProperty( + elIndex, 'title', + bind(ctx.otherCondition ? pureFunction1(4, ff1, ctx.title) : 'other title')); + } }, template: (rf: RenderFlags, ctx: HostBindingComp) => {} }); @@ -665,9 +678,10 @@ describe('host bindings', () => { consts: 0, vars: 0, hostVars: 1, - hostBindings: (dirIndex: number, elIndex: number) => { - elementProperty( - elIndex, 'id', bind(load(dirIndex).foos.length)); + hostBindings: (rf: RenderFlags, ctx: HostBindingWithContentChildren, elIndex: number) => { + if (rf & RenderFlags.Update) { + elementProperty(elIndex, 'id', bind(ctx.foos.length)); + } }, contentQueries: (dirIndex) => { registerContentQuery(query(null, ['foo']), dirIndex); }, contentQueriesRefresh: (dirIndex: number, queryStartIdx: number) => { @@ -721,8 +735,10 @@ describe('host bindings', () => { consts: 0, vars: 0, hostVars: 1, - hostBindings: (dirIndex: number, elIndex: number) => { - elementProperty(elIndex, 'id', bind(load(dirIndex).myValue)); + hostBindings: (rf: RenderFlags, ctx: HostBindingWithContentHooks, elIndex: number) => { + if (rf & RenderFlags.Update) { + elementProperty(elIndex, 'id', bind(ctx.myValue)); + } }, template: (rf: RenderFlags, cmp: HostBindingWithContentHooks) => {} }); diff --git a/packages/core/test/render3/integration_spec.ts b/packages/core/test/render3/integration_spec.ts index 906bd65ec2..a252e9d911 100644 --- a/packages/core/test/render3/integration_spec.ts +++ b/packages/core/test/render3/integration_spec.ts @@ -447,10 +447,11 @@ describe('render3 integration test', () => { }, factory: () => cmptInstance = new TodoComponentHostBinding, hostVars: 1, - hostBindings: function(directiveIndex: number, elementIndex: number): void { - // host bindings - elementProperty( - elementIndex, 'title', bind(load(directiveIndex).title)); + hostBindings: function(rf: RenderFlags, ctx: any, elementIndex: number): void { + if (rf & RenderFlags.Update) { + // host bindings + elementProperty(elementIndex, 'title', bind(ctx.title)); + } } }); } @@ -1379,8 +1380,11 @@ describe('render3 integration test', () => { return hostBindingDir = new HostBindingDir(); }, hostVars: 1, - hostBindings: function HostBindingDir_HostBindings(dirIndex: number, elIndex: number) { - elementAttribute(elIndex, 'aria-label', bind(load(dirIndex).label)); + hostBindings: function HostBindingDir_HostBindings( + rf: RenderFlags, ctx: any, elIndex: number) { + if (rf & RenderFlags.Update) { + elementAttribute(elIndex, 'aria-label', bind(ctx.label)); + } } }); } diff --git a/packages/core/test/render3/ivy/jit_spec.ts b/packages/core/test/render3/ivy/jit_spec.ts index 63c06deb22..91e656481e 100644 --- a/packages/core/test/render3/ivy/jit_spec.ts +++ b/packages/core/test/render3/ivy/jit_spec.ts @@ -229,7 +229,7 @@ ivyEnabled && describe('render3 jit', () => { const cmpDef = (Cmp as any).ngComponentDef as ComponentDef; expect(cmpDef.hostBindings).toBeDefined(); - expect(cmpDef.hostBindings !.length).toBe(2); + expect(cmpDef.hostBindings !.length).toBe(3); }); it('should compile @Pipes without errors', () => { diff --git a/packages/core/test/render3/listeners_spec.ts b/packages/core/test/render3/listeners_spec.ts index f35e593a5a..fc460cebba 100644 --- a/packages/core/test/render3/listeners_spec.ts +++ b/packages/core/test/render3/listeners_spec.ts @@ -449,7 +449,42 @@ describe('event listeners', () => { expect(comp.counters).toEqual([1, 1]); }); - it('should support host listeners', () => { + it('should support host listeners on components', () => { + let events: string[] = []; + class MyComp { + /* @HostListener('click') */ + onClick() { events.push('click!'); } + + static ngComponentDef = defineComponent({ + type: MyComp, + selectors: [['comp']], + consts: 1, + vars: 0, + template: function CompTemplate(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + text(0, 'Some text'); + } + }, + factory: () => { return new MyComp(); }, + hostBindings: function HostListenerDir_HostBindings( + rf: RenderFlags, ctx: any, elIndex: number) { + if (rf & RenderFlags.Create) { + listener('click', function() { return ctx.onClick(); }); + } + } + }); + } + + const fixture = new ComponentFixture(MyComp); + const host = fixture.hostElement; + host.click(); + expect(events).toEqual(['click!']); + + host.click(); + expect(events).toEqual(['click!', 'click!']); + }); + + it('should support host listeners on directives', () => { let events: string[] = []; class HostListenerDir { @@ -459,11 +494,13 @@ describe('event listeners', () => { static ngDirectiveDef = defineDirective({ type: HostListenerDir, selectors: [['', 'hostListenerDir', '']], - factory: function HostListenerDir_Factory() { - const $dir$ = new HostListenerDir(); - listener('click', function() { return $dir$.onClick(); }); - return $dir$; - }, + factory: function HostListenerDir_Factory() { return new HostListenerDir(); }, + hostBindings: function HostListenerDir_HostBindings( + rf: RenderFlags, ctx: any, elIndex: number) { + if (rf & RenderFlags.Create) { + listener('click', function() { return ctx.onClick(); }); + } + } }); } diff --git a/packages/core/test/render3/view_container_ref_spec.ts b/packages/core/test/render3/view_container_ref_spec.ts index 8e43454e8f..cae87ecc29 100644 --- a/packages/core/test/render3/view_container_ref_spec.ts +++ b/packages/core/test/render3/view_container_ref_spec.ts @@ -1828,9 +1828,10 @@ describe('ViewContainerRef', () => { template: (rf: RenderFlags, cmp: HostBindingCmpt) => {}, hostVars: 1, attributes: ['id', 'attribute'], - hostBindings: function(dirIndex, elIndex) { - const cmptInstance = load(dirIndex); - elementProperty(elIndex, 'title', bind(cmptInstance.title)); + hostBindings: function(rf: RenderFlags, ctx: HostBindingCmpt, elIndex: number) { + if (rf & RenderFlags.Update) { + elementProperty(elIndex, 'title', bind(ctx.title)); + } }, }); }