diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 60e6f4e482..d74e15d715 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -1202,7 +1202,6 @@ export function hostElement( return node; } - /** * Adds an event listener to the current node. * @@ -1215,29 +1214,35 @@ export function hostElement( */ export function listener( eventName: string, listenerFn: (e?: any) => any, useCapture = false): void { - ngDevMode && assertPreviousIsParent(); - ngDevMode && assertNodeOfPossibleTypes(previousOrParentNode, TNodeType.Element); + ngDevMode && + assertNodeOfPossibleTypes( + previousOrParentNode, TNodeType.Element, TNodeType.Container, TNodeType.ElementContainer); const node = previousOrParentNode; - const native = node.native as RElement; - ngDevMode && ngDevMode.rendererAddEventListener++; - // In order to match current behavior, native DOM event listeners must be added for all - // events (including outputs). - if (isProceduralRenderer(renderer)) { - const wrappedListener = wrapListenerWithDirtyLogic(viewData, listenerFn); - const cleanupFn = renderer.listen(native, eventName, wrappedListener); - storeCleanupFn(viewData, cleanupFn); - } else { - const wrappedListener = wrapListenerWithDirtyAndDefault(viewData, listenerFn); - native.addEventListener(eventName, wrappedListener, useCapture); - const cleanupInstances = getCleanup(viewData); - cleanupInstances.push(wrappedListener); - if (firstTemplatePass) { - getTViewCleanup(viewData).push( - eventName, node.tNode.index, cleanupInstances !.length - 1, useCapture); + // add native event listener - applicable to elements only + if (previousOrParentNode.tNode.type === TNodeType.Element) { + const native = node.native as RElement; + ngDevMode && ngDevMode.rendererAddEventListener++; + + // In order to match current behavior, native DOM event listeners must be added for all + // events (including outputs). + if (isProceduralRenderer(renderer)) { + const wrappedListener = wrapListenerWithDirtyLogic(viewData, listenerFn); + const cleanupFn = renderer.listen(native, eventName, wrappedListener); + storeCleanupFn(viewData, cleanupFn); + } else { + const wrappedListener = wrapListenerWithDirtyAndDefault(viewData, listenerFn); + native.addEventListener(eventName, wrappedListener, useCapture); + const cleanupInstances = getCleanup(viewData); + cleanupInstances.push(wrappedListener); + if (firstTemplatePass) { + getTViewCleanup(viewData).push( + eventName, node.tNode.index, cleanupInstances !.length - 1, useCapture); + } } } + // subscribe to directive outputs let tNode: TNode|null = node.tNode; if (tNode.outputs === undefined) { // if we create TNode here, inputs must be undefined so we know they still need to be diff --git a/packages/core/test/render3/directive_spec.ts b/packages/core/test/render3/directive_spec.ts index 3d7ee5deab..cf9076d20d 100644 --- a/packages/core/test/render3/directive_spec.ts +++ b/packages/core/test/render3/directive_spec.ts @@ -8,10 +8,11 @@ import {EventEmitter} from '@angular/core'; -import {AttributeMarker, defineDirective} from '../../src/render3/index'; -import {bind, element, elementEnd, elementProperty, elementStart, listener, loadDirective} from '../../src/render3/instructions'; +import {AttributeMarker, RenderFlags, defineDirective} from '../../src/render3/index'; -import {TemplateFixture} from './render_util'; +import {bind, element, elementEnd, elementProperty, elementStart, listener, template, elementContainerStart, elementContainerEnd} from '../../src/render3/instructions'; + +import {ComponentFixture, TemplateFixture, createComponent} from './render_util'; describe('directive', () => { @@ -149,6 +150,67 @@ describe('directive', () => { expect(fixture.html).toEqual(''); expect(directiveInstance !).not.toBeUndefined(); }); + }); + + describe('outputs', () => { + + let directiveInstance: Directive; + + class Directive { + static ngDirectiveDef = defineDirective({ + type: Directive, + selectors: [['', 'out', '']], + factory: () => directiveInstance = new Directive, + outputs: {out: 'out'} + }); + + out = new EventEmitter(); + } + + it('should allow outputs of directive on ng-template', () => { + /** + * + */ + const Cmpt = createComponent('Cmpt', function(rf: RenderFlags, ctx: {value: any}) { + if (rf & RenderFlags.Create) { + template(0, null, 0, 0, null, [AttributeMarker.SelectOnly, 'out']); + listener('out', () => { ctx.value = true; }); + } + }, 1, 0, [Directive]); + + const fixture = new ComponentFixture(Cmpt); + + expect(directiveInstance !).not.toBeUndefined(); + expect(fixture.component.value).toBeFalsy(); + + directiveInstance !.out.emit(); + fixture.update(); + expect(fixture.component.value).toBeTruthy(); + }); + + it('should allow outputs of directive on ng-container', () => { + /** + * + */ + const Cmpt = createComponent('Cmpt', function(rf: RenderFlags, ctx: {value: any}) { + if (rf & RenderFlags.Create) { + elementContainerStart(0, [AttributeMarker.SelectOnly, 'out']); + { + listener('out', () => { ctx.value = true; }); + } + elementContainerEnd(); + } + }, 1, 0, [Directive]); + + const fixture = new ComponentFixture(Cmpt); + + expect(directiveInstance !).not.toBeUndefined(); + expect(fixture.component.value).toBeFalsy(); + + directiveInstance !.out.emit(); + fixture.update(); + expect(fixture.component.value).toBeTruthy(); + }); }); }); diff --git a/packages/core/test/render3/integration_spec.ts b/packages/core/test/render3/integration_spec.ts index a14a48af9e..3ee9e1c101 100644 --- a/packages/core/test/render3/integration_spec.ts +++ b/packages/core/test/render3/integration_spec.ts @@ -862,25 +862,6 @@ describe('render3 integration test', () => { expect(fixture.html).toEqual('
'); }); - it('should throw when trying to add event listener', () => { - /** - *
- */ - function Template() { - elementStart(0, 'div'); - { - elementContainerStart(1); - { - listener('click', function() {}); - } - elementContainerEnd(); - } - elementEnd(); - } - - expect(() => { new TemplateFixture(Template, () => {}, 2); }).toThrow(); - }); - }); describe('tree', () => {